Il existe plusieurs langages pour décrire la structure d'un
document
On l'a dit, il n'existe pas d'accord dans le monde
JSON est normalisé dans le
Les fans de
La syntaxe exacte est spécifiée en section 2, une fois que vous
avez (re)lu le
properties = (with-properties // with-optional-properties)
with-properties = (
properties: { * tstr => { schema }},
? optionalProperties: { * tstr => { schema }},
? additionalProperties: bool,
shared,
)
Ce qui veut dire en langage naturel que le schéma JTD peut avoir un
membre
{
"properties": {
"name": {
"type": "string"
},
"ok": {
"type": "boolean",
"nullable": true
},
"level": {
"type": "int32"
}
}
}
Ce schéma accepte le document JSON :
{
"name": "Foobar",
"ok": false,
"level": 1
}
Ou bien ce document :
{
"name": "Durand",
"ok": null,
"level": 42
}
(L'élément
{
"name": "Zig",
"ok": true,
"level": 0,
"extra": true
}
Car il y a un membre de trop,
Ce document JSON ne sera pas accepté non plus :
{
"name": "Invalid",
"ok": true
}
Car la propriété
Un exemple plus détaillé, et pour un cas réel, figure dans
l'annexe C du RFC, en utilisant le langage du
JTD ne permet pas de vrai mécanisme d'extension mais on peut
toujours ajouter un membre
Jouons d'ailleurs un peu avec une mise en œuvre de JTD. Vous en
trouverez plusieurs
ici, pour divers langages de programmation. Essayons avec
celui en
% git clone https://github.com/jsontypedef/json-typedef-python.git
% cd json-typedef-python
% python setup.py build
% python setup.py install --user
(Oui, on aurait pu utiliser
#!/usr/bin/env python3
import sys
import json
import jtd
if len(sys.argv) != 3:
raise Exception("Usage: %s schema json-file" % sys.argv[0])
textSchema = open(sys.argv[1], 'r').read()
textJsonData = open(sys.argv[2], 'r').read()
schema = jtd.Schema.from_dict(json.loads(textSchema))
jsonData = json.loads(textJsonData)
result = jtd.validate(schema=schema, instance=jsonData)
print(result)
Si le fichier JSON correspond au schéma, il affichera un tableau
vide, sinon un tableau contenant la liste des erreurs :
% ./jtd.py myschema.json mydoc1.json
[]
(
% ./jtd.py myschema.json mydoc3.json
[ValidationError(instance_path=['extra'], schema_path=[])]
(
Une particularité de JTD est de normaliser le mécanisme de
signalement d'erreurs. Les erreurs doivent être formatées en JSON
(évidemment…) avec un membre
JTD est spécifié en CDDL donc on peut tester ses schémas avec les
outils CDDL, ici un outil en
% gem install cddl --user
Ensuite, on peut valider ses schémas :
% cddl jtd.cddl validate myschema.json
%
Si le schéma a une erreur (ici, j'ai utilisé le type
% cddl jtd.cddl validate wrongschema.json
CDDL validation failure (nil for {"properties"=>{"name"=>{"type"=>"char"}, "ok"=>{"type"=>"boolean", "nullable"=>true}, "level"=>{"type"=>"int32"}}}):
["char", [:text, "timestamp"], nil]
["char", [:text, "timestamp"], null]
(Oui, les messages d'erreur de l'outil
Et avec l'exemple de l'annexe C, le reputon du
% cddl jtd.cddl validate reputon.json
%
C'est parfait, le schéma du RFC est correct, validons le fichier
JSON tiré de la section 6.4 du
% ./jtd.py reputon.json r1.json
[]
Si jamais il y a une erreur (ici, on a enlevé le membre
% ./jtd.py reputon.json r1.json
[ValidationError(instance_path=['reputons', '0'], schema_path=['properties', 'reputons', 'elements', 'properties', 'rating'])]
Une intéressante annexe B fait une comparaison de JTD
avec CDDL. Par exemple, le schéma CDDL :
root = "PENDING" / "DONE" / "CANCELED"
accepterait les mêmes documents que le schéma JTD :
{ "enum": ["PENDING", "DONE", "CANCELED"]}
Et celui-ci, en CDDL (où le
root = { a: bool, b: number, ? c: tstr, ? d: tdate }
reviendrait à ce schéma JTD :
{
"properties": {
"a": {
"type": "boolean"
},
"b": {
"type": "float32"
}
},
"optionalProperties": {
"c": {
"type": "string"
},
"d": {
"type": "timestamp"
}
}
}
Merci à Ulysse Carion pour sa relecture.