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:
minLengthimaxLength, restriccionen la longitud del text. - Per a números:
minimumimaximum, 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 enproperties.{ }é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:
propertiesdiu com ha de ser una propietat.requireddiu 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:
propertiesdefineix quins camps poden existir i com han de ser.requiredindica quins han d’existir obligatòriament.additionalPropertiesindica 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
definitionsi$refsolen 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/Personaindica 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:
$refpermet 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,
25no és un string, per tant la validació falla.
["Anna", "Pere", true]
- Perquè
trueno és un string.
["Anna", null]
- Perquè
nulltampoc é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è
trueno és ni string ni number.
["Anna", null, 30]
- Perquè
nullno està inclòs en eltype.
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 apropertiesperò aplicat a arrays: defineix la regla que han de complir els elements de la llista.
minItems i maxItems
minItemsindica el nombre mínim d’elements que ha de tindre l’array.maxItemsindica 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:
minItemsimaxItemsnomés controlen la quantitat.itemscontinua controlant el tipus de cada element.
La validació es fa en dos nivells:
- Es comprova que el nombre d’elements estiga dins del rang permés.
- 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
minItemsnimaxItems, 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:
minItemsimaxItemscontrolen la quantitat.itemscontrola el tipus. Quanitemsés una llista, cada posició té la seua pròpia regla i l’ordre importa.
enum
enumpermet 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:
enumcrea 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.\dsignifica “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:
patternvalida la forma d’un text mitjançant una expressió regular. No valida el significat real del valor, només que complisca el patró indicat.