JSON Schema

Quan treballem amb JSON, normalment fem coses com:

  • Enviar dades a una API
  • Guardar informació en una base de dades
  • Llegir fitxers de configuració
  • Intercanviar dades entre aplicacions

Un JSON pot ser sintàcticament correcte, però tindre dades incorrectes.

Exemple:

{
  "nom": 123,
  "edat": "vint"
}

Este JSON és vàlid sintàcticament. Però està malament estructurat si esperàvem:

  • nom → string
  • edat → integer

Necessitem una manera de dir:

“Les dades han de tindre esta forma exacta.”

Això és JSON Schema.

JSON només comprova que el format siga correcte (claus, cometes, comes). No comprova que els tipus siguen adequats ni que existisquen totes les propietats necessàries. JSON Schema s’utilitza per validar l’estructura i el contingut abans d’utilitzar les dades en el programa.


Què és JSON Schema?

JSON Schema és un document JSON que defineix com ha de ser un altre JSON.

No conté dades.

Conté regles.

És com un contracte que diu:

  • Quin tipus té cada propietat
  • Quines propietats són obligatòries
  • Quins valors estan permesos
  • Quantes dades hi pot haver

Si el JSON compleix les regles → vàlid. Si no les compleix → error.

Un esquema no transforma ni modifica dades. Només valida que el document JSON compleix les condicions definides. Quan una validació falla, normalment es mostra un missatge indicant quina regla no s’ha complit.


Estructura mínima d’un JSON Schema

La paraula clau $schema serveix per indicar quina versió de l’estàndard JSON Schema estem utilitzant. És una forma de dir a la ferramenta de validació quines regles ha d’aplicar.

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object"
}

Açò significa:

  • Estem usant l’estàndard draft-07
  • El document validat ha de ser un objecte

$schema indica la versió de l’especificació que s’està utilitzant. "type": "object" indica que el JSON que validarem ha de ser un objecte, és a dir, ha de començar amb { } i contindre parelles clau-valor.

Si intentem validar un array o un string contra este esquema, donarà error perquè no és un objecte.


type – Tipus de dades

type és la propietat més bàsica i important de JSON Schema. Serveix per indicar quin tipus de dada és vàlid en un punt concret del document JSON que estem validant.

Quan definim type, estem establint una restricció directa sobre el valor: si el tipus no coincideix exactament amb el que s’ha indicat, la validació fallarà.

Els tipus bàsics són:

  • string → text
  • number → decimal
  • integer → enter
  • boolean → true o false
  • object → objecte JSON
  • array → llista
  • null → valor nul

Exemple:

{
  "type": "string"
}

Només acceptarà cadenes de text.

type indica quin tipus de dada és vàlid en el punt on s’utilitza. Si es defineix "type": "integer", qualsevol valor que no siga un número enter farà que la validació falle.


Important: null és un tipus propi

En JSON, null és un tipus independent, igual que string, number o boolean.

Si definim:

{
  "type": "string"
}

El valor null no serà vàlid, perquè no és un string. En JSON Schema, els tipus es validen de manera estricta i no hi ha conversions automàtiques.

Si volem permetre que un camp continga un text o bé no tinga valor, hem d’indicar-ho explícitament:

{
  "type": ["string", "null"]
}

Això significa que el camp pot ser un text o pot ser null.


Restriccions habituals en strings i números

En JSON Schema, a més de type, podem afegir restriccions molt comunes:

  • Per a strings: minLength i maxLength, restriccionen la longitud del text.
  • Per a números: minimum i maximum, restriccionen el rang de valors.

Exemple (string amb longitud controlada):

{
  "type": "string",
  "minLength": 3,
  "maxLength": 10
}

Seria vàlid un text com “Hola” (4 caràcters), però no seria vàlid un text com “Jo” (2 caràcters, menor que el mínim) ni “Benvinguts a tots” (17 caràcters, major que el màxim).

Exemple (número amb rang controlat):

{
  "type": "number",
  "minimum": 0,
  "maximum": 100
}

Seria vàlid un número com 25, però no seria vàlid un número com -5 (menor que el mínim) ni 150 (major que el màxim).

Objectes i propietats

Si el JSON és un objecte, utilitzem:

  • properties
  • required
  • additionalProperties

properties

Defineix les propietats i el seu tipus.

{
  "type": "object",
  "properties": {
    "nom": { "type": "string" },
    "edat": { "type": "integer" }
  }
}

Açò defineix com han de ser si apareixen.

No obliga a que existisquen.

properties és un objecte que descriu cada camp possible i el seu tipus. Serveix per indicar la forma que ha de tindre cada propietat si apareix dins del JSON.


required

Obliga a que existisquen determinades propietats.

{
  "type": "object",
  "properties": {
    "nom": { "type": "string" }
  },
  "required": ["nom"]
}

Vàlid:

{ "nom": "Anna" }

Invàlid:

{ }

required és un array amb els noms de les propietats obligatòries. Si falta alguna propietat indicada en esta llista, el JSON no serà vàlid encara que la resta siga correcta.

required no defineix tipus ni estructura interna. Només comprova si la propietat existeix dins del JSON.

En este exemple, "nom" és obligatori. Per tant:

  • { "nom": "Anna" } és vàlid perquè la propietat existeix i complix el tipus definit en properties.
  • { } és invàlid perquè falta la propietat obligatòria, encara que no hi haja cap error de sintaxi.

És important entendre que required treballa conjuntament amb properties:

  • properties diu com ha de ser una propietat.
  • required diu si ha d’existir o no.

Sense required, totes les propietats definides en properties serien opcionals.


Diferència clau

  • properties → defineix forma
  • required → obliga existència

properties estableix les regles de tipus i estructura. required estableix quines propietats han d’aparéixer obligatòriament.

Una propietat pot estar definida en properties però no estar en required, i per tant ser opcional. En eixe cas:

  • Si no apareix → no hi ha error.
  • Si apareix → haurà de complir el tipus indicat.

Aquesta separació permet controlar de manera flexible quins camps són obligatoris i quins són opcionals.


additionalProperties

Controla si es permeten propietats no definides.

Per defecte, JSON Schema permet propietats addicionals que no estiguen declarades en properties. Això significa que, si no indiquem res, el JSON pot incloure camps extra i continuarà sent vàlid.

Per exemple:

{
  "type": "object",
  "properties": {
    "nom": { "type": "string" }
  }
}

Este esquema només defineix la propietat "nom", però no diu res sobre les propietats addicionals.

El següent JSON és vàlid:

{
  "nom": "Anna",
  "edat": 20
}

Encara que "edat" no està definida en properties, la validació passa perquè el comportament per defecte és permissiu.


No permetre propietats extra

{
  "type": "object",
  "properties": {
    "nom": { "type": "string" }
  },
  "additionalProperties": false
}

Invàlid:

{
  "nom": "Anna",
  "edat": 20
}

En este esquema només està definida la propietat "nom". En posar "additionalProperties": false", estem indicant que no es permet cap altra propietat fora de les definides en properties.

Ara el mateix JSON que abans era vàlid es converteix en invàlid, perquè "edat" no està declarada en l’esquema.

additionalProperties completa el control de l’objecte:

  • properties defineix quins camps poden existir i com han de ser.
  • required indica quins han d’existir obligatòriament.
  • additionalProperties indica si es permeten camps no previstos.

Això és útil quan volem controlar estrictament l’estructura i evitar camps inesperats o errors de nom.

NOTA: No és necessari escriure "additionalProperties": true, perquè ja és el comportament per defecte.


Reutilització d’esquemes amb $ref

Quan un esquema creix, pot ser necessari reutilitzar la mateixa estructura en diversos llocs.

En lloc de repetir el mateix bloc de codi, JSON Schema permet definir un subesquema i reutilitzar-lo mitjançant $ref.

Això fa que l’esquema siga:

  • Més clar
  • Més modular
  • Més fàcil de mantindre

definitions i $ref solen anar dins del mateix esquema.


definitions

definitions permet guardar un subesquema amb un nom.

{
  "definitions": {
    "Persona": {
      "type": "object",
      "properties": {
        "nom": { "type": "string" },
        "edat": { "type": "integer", "minimum": 0 }
      },
      "required": ["nom", "edat"],
      "additionalProperties": false
    }
  }
}

Ací hem definit un esquema anomenat "Persona".


$ref

$ref permet reutilitzar un esquema definit anteriorment.

{
  "type": "object",
  "definitions": {
    "Persona": {
      "type": "object",
      "properties": {
        "nom": { "type": "string" },
        "edat": { "type": "integer", "minimum": 0 }
      },
      "required": ["nom", "edat"],
      "additionalProperties": false
    }
  },
  "properties": {
    "responsable": {
      "$ref": "#/definitions/Persona"
    }
  },
  "required": ["responsable"],
  "additionalProperties": false
}

En este exemple:

  • Definim un subesquema anomenat "Persona".
  • En la propietat "responsable" reutilitzem eixe esquema amb $ref.
  • #/definitions/Persona indica que la referència és interna (dins del mateix document).

Exemple vàlid

{
  "responsable": {
    "nom": "Anna",
    "edat": 25
  }
}

Exemple invàlid

{
  "responsable": {
    "nom": "Anna"
  }
}

És invàlid perquè falta la propietat "edat", que és obligatòria en el subesquema "Persona".


En resum: $ref permet reutilitzar esquemes definits prèviament. Evita repetir estructura i facilita la modularitat del document.

Arrays

Quan el tipus és "type": "array", estem indicant que el JSON que validarem ha de ser una llista, és a dir, un conjunt de valors entre [ ].

Però igual que passa amb els objectes, no és suficient dir que és un array. També hem d’indicar:

  • Quin tipus d’elements pot contindre.
  • Quants elements pot tindre.
  • Si cada posició té una regla concreta.

Per a això utilitzem items, minItems i maxItems.


items

Defineix el tipus dels elements.

{
  "type": "array",
  "items": { "type": "string" }
}

Tots els elements han de ser string.

items indica quina regla han de complir els elements de la llista. Cada element de l’array es valida segons l’esquema definit dins de items.

En este exemple:

  • Si un element no és un string, la validació fallarà.
  • No importa la posició, tots els elements han de complir la mateixa regla.

Exemples vàlids:

["Anna", "Pere", "Laura"]
["A", "B", "C", "D"]

Exemples invàlids:

["Anna", 25]
  • En este cas, 25 no és un string, per tant la validació falla.
["Anna", "Pere", true]
  • Perquè true no és un string.
["Anna", null]
  • Perquè null tampoc és un string (excepte que es permeta explícitament).

Si volem permetre més d’un tipus en cada element, també es pot definir així:

{
  "type": "array",
  "items": { "type": ["string", "number"] }
}

En este cas, cada element pot ser un string o un number.

Exemple vàlids

["Anna", 25, "Pere", 30]

Exemples invàlids:

["Anna", true, 30]
  • Perquè true no és ni string ni number.
["Anna", null, 30]
  • Perquè null no està inclòs en el type.

Si posem:

"items": { "type": "number" }

Tots els valors de l’array han de ser números.

Exemple vàlid:

[10, 20, 30]

Exemple invàlid:

[10, "20", 30]
  • Perquè "20" és un string i no un number.

Sense items, l’array podria contindre qualsevol tipus de valor, per exemple:

["Anna", 25, true, null]

I continuaria sent vàlid, perquè no hi ha cap regla que limite el tipus dels elements.

En resum: items és l’equivalent a properties però aplicat a arrays: defineix la regla que han de complir els elements de la llista.


minItems i maxItems

  • minItems indica el nombre mínim d’elements que ha de tindre l’array.
  • maxItems indica el màxim.
{
  "type": "array",
  "items": { "type": "number" },
  "minItems": 2,
  "maxItems": 4
}

Controla quants elements hi ha. Si l’array té menys o més elements dels permesos, la validació falla.

És important entendre que:

  • minItems i maxItems només controlen la quantitat.
  • items continua controlant el tipus de cada element.

La validació es fa en dos nivells:

  1. Es comprova que el nombre d’elements estiga dins del rang permés.
  2. Es comprova que cada element complisca el tipus definit en items.

A l’exemple anterio:

Són vàlids:

[1, 2]
[1, 2, 3, 4]

Són invàlids:

[1]
  • Perquè no arriba al mínim de 2 elements.
[1, 2, 3, 4, 5]
  • Perquè supera el màxim de 4 elements.
[1, "2"]
  • Encara que té 2 elements (quantitat correcta), "2" no és un number, per tant falla per tipus.
[]
  • És invàlid perquè no arriba al mínim requerit.

Aquestes restriccions són útils quan necessitem una quantitat concreta o limitada d’elements, per exemple:

  • Coordenades (exactament 2 valors).
  • Llistes amb un mínim obligatori.
  • Arrays amb un màxim definit.
  • Respostes on només es permet un nombre concret d’opcions.

NOTA: Si no definim minItems ni maxItems, l’array pot tindre qualsevol nombre d’elements.


Validació per posició

Quan items és una llista, cada posició té una regla diferent. Això s’anomena validació per posició (tuple validation).

{
  "type": "array",
  "items": [
    { "type": "string" },
    { "type": "number" }
  ]
}
  • Primer element → string
  • Segon element → number

A diferència del cas anterior, on tots els elements seguien la mateixa regla, ací:

  • La posició 0 té una regla concreta -> El primer element ha de ser un string.
  • La posició 1 té una altra regla diferent -> El segon element ha de ser un number.

Exemple vàlid:

["Anna", 25]

Exemples invàlids:

[25, "Anna"]
  • L’ordre és incorrecte.
["Anna"]
  • Falta el segon element.
  • ["Anna", 25, true]
    
  • Hi ha un tercer element no definit en la llista d’items.

L’ordre és important i cada element es valida segons la seua posició.

Si l’array té més elements dels definits en la llista d’items, el comportament dependrà de si s’utilitza o no additionalItems.

Per defecte, els elements extra estan permesos. Si volem impedir-ho, cal indicar-ho explícitament.

additionalItems

Quan items és una llista (validació per posició), podem controlar si es permeten elements addicionals.

Per defecte, si hi ha més elements dels definits en items, estan permesos.

Si volem impedir-ho, utilitzem additionalItems: false.

{
  "type": "array",
  "items": [
    { "type": "string" },
    { "type": "number" }
  ],
  "additionalItems": false
}

En este cas, només es permeten dos elements. Si l’array té un tercer element, la validació fallarà.

["Anna", 25, true]

És invàlid perquè amb additionalItems: false no es permet cap element més dels definits en items. —

En resum: minItems i maxItems controlen la quantitat. items controla el tipus. Quan items és una llista, cada posició té la seua pròpia regla i l’ordre importa.

enum

  • enum permet limitar els valors possibles.
  • Defineix una llista tancada de valors vàlids.
  • El valor ha de coincidir exactament amb un dels elements de la llista.
{
  "type": "string",
  "enum": ["normal", "dark"]
}

Només pot ser "normal" o "dark".

Este esquema normalment s’utilitza dins d’un objecte, per exemple per validar un camp que només admet uns quants valors possibles.

Exemple d’ús dins d’un objecte:

{
  "type": "object",
  "properties": {
    "mode": {
      "type": "string",
      "enum": ["normal", "dark"]
    }
  },
  "required": ["mode"],
  "additionalProperties": false
}

Exemple vàlid:

{ "mode": "dark" }

Exemple invàlid:

{ "mode": "clar" }

Perquè "clar" no està dins de la llista d’enum.

NOTA: És comparació exacta.

  • El valor ha de ser exactament igual a un dels indicats.
  • Es respecten majúscules i minúscules.
  • No es permeten variants semblants.

Exemples vàlids:

"normal"
"dark"

Exemples invàlids:

"Normal"
  • No coincidix exactament (majúscula diferent).
"dark-mode"
  • No està dins de la llista definida.
null
  • No és un string vàlid dins de l’enum.

enum no valida formats ni tipus per si mateix, sinó que restringix els valors possibles. Normalment es combina amb type per assegurar també el tipus.

També pot utilitzar-se amb altres tipus:

{
  "type": "number",
  "enum": [1, 2, 3]
}

En este cas, només es permeten els números 1, 2 o 3.

Exemple vàlid:

2

Exemple invàlid:

4

En resum: enum crea una llista tancada de valors permesos. El valor ha de coincidir exactament amb un dels indicats.


pattern (Expressions regulars)

Permet validar el format d’un string.

pattern utilitza una expressió regular per comprovar que el text complix un format determinat.

Exemple data:

{
  "type": "string",
  "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
}

Este patró indica que el text ha de tindre:

  • quatre dígits
  • un guió
  • dos dígits
  • un guió
  • dos dígits

És a dir, un format tipus: AAAA-MM-DD.

En esta expressió regular:

  • ^ indica l’inici del text.
  • $ indica el final del text.
  • \d significa “qualsevol dígit” (0-9).
  • {4} o {2} indiquen quantes vegades s’ha de repetir el patró anterior.

Important: En JSON, la barra invertida s’ha d’escriure com \\ perquè \ és caràcter d’escapament.

Exemples vàlids:

"2024-05-17"
"1999-12-01"

Exemples invàlids:

"24-05-17"
  • No té quatre dígits inicials.
"2024/05/17"
  • Utilitza / en lloc de -.
"2024-5-7"
  • No té dos dígits en mes i dia.

pattern valida la forma del text, no el seu significat real.

Per exemple:

"2024-99-99"

Passaria la validació del patró, encara que no siga una data real. Perquè l’expressió regular només comprova l’estructura, no si el valor té sentit.


També podem usar pattern per a altres formats, com per exemple un correu electrònic simplificat:

{
  "type": "string",
  "pattern": "^[^@]+@[^@]+\\.[^@]+$"
}

Açò obliga a que el text continga:

  • Un text abans de @
  • Un @
  • Un punt després

En resum: pattern valida la forma d’un text mitjançant una expressió regular. No valida el significat real del valor, només que complisca el patró indicat.


Table of contents