Cette bibliothèque est actuellement en mode maintenance et remplacée par erosb/json-sKema.
Ce référentiel ne verra aucune nouvelle fonctionnalité. Il fournit un support solide pour les versions draft-04, draft-06 et draft-07 de la spécification JSON Schema.
La dernière version 2020-12 est prise en charge uniquement par erosb/json-sKema.
Ce projet est une implémentation des spécifications JSON Schema Draft v4, Draft v6 et Draft v7. Il utilise l'API org.json (créée par Douglas Crockford) pour représenter les données JSON.
Supposons que vous savez déjà ce qu'est le schéma JSON et que vous souhaitez l'utiliser dans une application Java pour valider les données JSON. Mais - comme vous l'avez peut-être déjà découvert - il existe également une autre implémentation Java de la spécification JSON Schema. Voici donc quelques conseils pour savoir lequel utiliser :
Ajoutez la dépendance suivante à votre pom.xml
:
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema</ artifactId >
< version >1.14.4</ version >
</ dependency >
Note sur les anciennes versions : les versions entre 1.6.0
et 1.9.1
ne peuvent être trouvées que sur JitPack avec les coordonnées com.github.everit-org.json-schema:org.everit.json.schema
. Les versions 1.0.0
... 1.5.1
sont disponibles sur Maven Central sous les coordonnées org.everit.json:org.everit.json.schema
.
Il y a eu quelques tentatives pour faire fonctionner la bibliothèque sur Java 6/7.
Un portage java6 de la version 1.9.2 a été développé par @mindbender1 et il est accessible via Maven Central avec les coordonnées suivantes :
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema-jdk6</ artifactId >
< version >1.9.2</ version >
</ dependency >
Rétroportages des anciennes versions :
com.doctusoft:json-schema-java7:1.4.1
com.github.rdruilhe.json-schema:org.everit.json.schema:1.1.1
import org . everit . json . schema . Schema ;
import org . everit . json . schema . loader . SchemaLoader ;
import org . json . JSONObject ;
import org . json . JSONTokener ;
// ...
try ( InputStream inputStream = getClass (). getResourceAsStream ( "/path/to/your/schema.json" )) {
JSONObject rawSchema = new JSONObject ( new JSONTokener ( inputStream ));
Schema schema = SchemaLoader . load ( rawSchema );
schema . validate ( new JSONObject ( "{ " hello " : " world " }" )); // throws a ValidationException if this object is invalid
}
JSON Schema propose actuellement 4 versions majeures, Draft 3, Draft 4, Draft 6 et Draft 7. Cette bibliothèque implémente les 3 plus récentes, vous pouvez jeter un coup d'œil rapide aux différences ici et ici. Étant donné que les deux versions présentent un certain nombre de différences – et que la version 6 n’est pas rétrocompatible avec la version 4 – il est bon de savoir quelle version vous utiliserez.
La meilleure façon d'indiquer la version du schéma JSON que vous souhaitez utiliser est d'inclure son URL de méta-schéma dans la racine du document avec la clé "$schema"
. Il s'agit d'une notation courante, facilitée par la bibliothèque pour déterminer quelle version doit être utilisée.
Référence rapide :
"$schema": "http://json-schema.org/draft-04/schema"
dans la racine du schéma, alors Draft 4 sera utilisé"$schema": "http://json-schema.org/draft-06/schema"
dans la racine du schéma, alors Draft 6 sera utilisé"$schema": "http://json-schema.org/draft-07/schema"
dans la racine du schéma, alors Draft 7 sera utiliséSi vous souhaitez spécifier explicitement la version du méta-schéma, vous pouvez modifier la valeur par défaut de Draft 4 à Draft 6/7 en configurant le chargeur de cette façon :
SchemaLoader loader = SchemaLoader . builder ()
. schemaJson ( yourSchemaJSON )
. draftV6Support () // or draftV7Support()
. build ();
Schema schema = loader . load (). build ();
À partir de la version 1.1.0
le validateur collecte toutes les violations de schéma (au lieu d'échouer immédiatement sur la première). Chaque échec est signalé par un pointeur JSON, pointant de la racine du document vers la partie en infraction. Si plusieurs violations de schéma ont été détectées, une ValidationException
sera levée sur les éléments parents les plus courants des violations, et chaque violation distincte peut être obtenue à l'aide de la méthode ValidationException#getCausingExceptions()
.
Pour démontrer les concepts ci-dessus, voyons un exemple. Considérons le schéma suivant :
{
"type" : " object " ,
"properties" : {
"rectangle" : { "$ref" : " #/definitions/Rectangle " }
},
"definitions" : {
"size" : {
"type" : " number " ,
"minimum" : 0
},
"Rectangle" : {
"type" : " object " ,
"properties" : {
"a" : { "$ref" : " #/definitions/size " },
"b" : { "$ref" : " #/definitions/size " }
}
}
}
}
Le document JSON suivant ne comporte qu'une seule violation du schéma (puisque « a » ne peut pas être négatif) :
{
"rectangle" : {
"a" : -5 ,
"b" : 5
}
}
Dans ce cas, la ValidationException
levée pointera vers #/rectangle/a
et ne contiendra pas de sous-exceptions :
try {
schema . validate ( rectangleSingleFailure );
} catch ( ValidationException e ) {
// prints #/rectangle/a: -5.0 is not higher or equal to 0
System . out . println ( e . getMessage ());
}
Maintenant, pour illustrer la manière dont les violations multiples sont gérées, considérons le document JSON suivant, dans lequel les propriétés « a » et « b » violent le schéma ci-dessus :
{
"rectangle" : {
"a" : -5 ,
"b" : " asd "
}
}
Dans ce cas, la ValidationException
levée pointera vers #/rectangle
, et elle aura 2 sous-exceptions, pointant vers #/rectangle/a
et #/rectangle/b
:
try {
schema . validate ( rectangleMultipleFailures );
} catch ( ValidationException e ) {
System . out . println ( e . getMessage ());
e . getCausingExceptions (). stream ()
. map ( ValidationException :: getMessage )
. forEach ( System . out :: println );
}
Cela imprimera le résultat suivant :
#/rectangle: 2 schema violations found
#/rectangle/a: -5.0 is not higher or equal to 0
#/rectangle/b: expected type: Number, found: String
Depuis la version 1.4.0
il est possible d'imprimer les instances ValidationException
sous forme de rapports d'échec au format JSON. La méthode ValidationException#toJSON()
renvoie une instance JSONObject
avec les clés suivantes :
"message"
: le message d'exception convivial pour les programmeurs (description de l'échec de validation)"keyword"
: le mot-clé du schéma JSON qui a été violé"pointerToViolation"
: un pointeur JSON désignant le chemin de la racine du document d'entrée vers son fragment qui a provoqué l'échec de validation"schemaLocation"
: un pointeur JSON désignant le chemin de la racine JSON du schéma vers le mot-clé violé"causingExceptions"
: un tableau (éventuellement vide) de sous-exceptions. Chaque sous-exception est représentée sous la forme d'un objet JSON, avec la même structure que celle décrite dans cette liste. Voir plus ci-dessus sur la création d'exceptions. Veuillez prendre en compte que le rapport d'échec complet est une structure arborescente hiérarchique : les sous-causes d'une cause peuvent être obtenues en utilisant #getCausingExceptions()
.
Les ValidationListener
peuvent servir à résoudre l'ambiguïté sur la façon dont une instance JSON correspond (ou ne correspond pas) à un schéma. Vous pouvez attacher une implémentation ValidationListener
au validateur pour recevoir des notifications d'événements sur les résultats intermédiaires de réussite/échec.
Exemple:
import org . everit . json . schema . Validator ;
...
Validator validator = Validator . builder ()
. withListener ( new YourValidationListenerImplementation ())
. build ();
validator . performValidation ( schema , input );
Les événements actuellement pris en charge :
"$ref"
en cours de résolution"allOf"
/ "anyOf"
/ "oneOf"
"allOf"
/ "anyOf"
/ "oneOf"
qui ne correspond pas"if"
"if"
qui ne correspond pas"then"
"then"
qui ne correspond pas"else"
"else"
qui ne correspond pas Voir la javadoc de l'interface org.everit.json.schema.event.ValidationListener
pour plus de détails. Les classes d'événements particulières ont également des implémentations #toJSON()
et #toString()
appropriées afin que vous puissiez les imprimer dans un format facilement analysable.
Par défaut, le rapport d'erreurs de validation en mode collecte (voir le chapitre "Enquêter sur les échecs"). C'est pratique pour avoir un rapport d'erreur détaillé, mais dans certaines circonstances, il est plus approprié d'arrêter la validation lorsqu'une panne est détectée sans vérifier le reste du document JSON. Pour activer ou désactiver ce mode de validation à échec rapide
Validator
pour votre schéma au lieu d'appeler Schema#validate(input)
failEarly()
de ValidatorBuilder
Exemple:
import org . everit . json . schema . Validator ;
...
Validator validator = Validator . builder ()
. failEarly ()
. build ();
validator . performValidation ( schema , input );
Remarque : la classe Validator
est immuable et thread-safe, vous n'avez donc pas besoin d'en créer une nouvelle pour chaque validation, il suffit de la configurer une seule fois.
Dans certains cas, lors de la validation de nombres ou de booléens, il est logique d'accepter des valeurs de chaîne analysables comme de telles primitives, car tout traitement successif analysera également automatiquement ces littéraux en valeurs numériques et logiques appropriées. De plus, les valeurs primitives non-chaînes sont triviales à convertir en chaînes, alors pourquoi ne pas autoriser les primitives json en tant que chaînes ?
Par exemple, prenons ce schéma :
{
"properties" : {
"booleanProp" : {
"type" : " boolean "
},
"integerProp" : {
"type" : " integer "
},
"nullProp" : {
"type" : " null "
},
"numberProp" : {
"type" : " number "
},
"stringProp" : {
"type" : " string "
}
}
}
Le document JSON suivant ne parvient pas à être validé, bien que toutes les chaînes puissent facilement être converties en valeurs appropriées :
{
"numberProp" : " 12.34 " ,
"integerProp" : " 12 " ,
"booleanProp" : " true " ,
"nullProp" : " null " ,
"stringProp" : 12.34
}
Dans ce cas, si vous souhaitez que l'instance ci-dessus réussisse la validation par rapport au schéma, vous devez utiliser la configuration de validation primitive indulgente activée. Exemple:
import org . everit . json . schema .*;
...
Validator validator = Validator . builder ()
. primitiveValidationStrategry ( PrimitiveValidationStrategy . LENIENT )
. build ();
validator . performValidation ( schema , input );
Remarque : en mode d'analyse indulgent, les 22 littéraux booléens possibles seront acceptés comme valeurs logiques.
La spécification JSON Schema définit le mot-clé « default » pour désigner les valeurs par défaut, bien qu'elle n'indique pas explicitement comment cela devrait affecter le processus de validation. Par défaut, cette bibliothèque ne définit pas les valeurs par défaut, mais si vous avez besoin de cette fonctionnalité, vous pouvez l'activer par la méthode SchemaLoaderBuilder#useDefaults(boolean)
, avant de charger le schéma :
{
"properties" : {
"prop" : {
"type" : " number " ,
"default" : 1
}
}
}
JSONObject input = new JSONObject ( "{}" );
System . out . println ( input . get ( "prop" )); // prints null
Schema schema = SchemaLoader . builder ()
. useDefaults ( true )
. schemaJson ( rawSchema )
. build ()
. load (). build ();
schema . validate ( input );
System . out . println ( input . get ( "prop" )); // prints 1
S'il manque certaines propriétés dans input
qui ont des valeurs "default"
dans le schéma, elles seront définies par le validateur lors de la validation.
Pour prendre en charge le mot-clé "regex"
du schéma JSON, la bibliothèque propose deux implémentations possibles :
java.util.regex
Bien que la bibliothèque RE2J offre des performances nettement supérieures à celles de java.util.regex
, elle n'est pas entièrement compatible avec la syntaxe prise en charge par java.util
ou ECMA 262. RE2J est donc recommandé si vous êtes préoccupé par les performances et ses limites sont acceptables.
L'implémentation RE2J peut être activée avec l'appel SchemaLoaderBuilder#regexpFactory()
:
SchemaLoader loader = SchemaLoader . builder ()
. regexpFactory ( new RE2JRegexpFactory ())
// ...
. build ();
Remarques :
pom.xml
afin qu'elle n'augmente pas inutilement la taille de votre artefactjava.util
a été utilisée, dans la 1.8.0, l'implémentation RE2J a été utilisée, et dans la 1.9.0, nous l'avons rendue configurable, en raison de certaines régressions signalées. La bibliothèque prend en charge les mots-clés readOnly
et writeOnly
qui sont apparus pour la première fois dans la version préliminaire 7. Si vous souhaitez utiliser cette fonctionnalité, avant la validation, vous devez indiquer au validateur si la validation a lieu dans un contexte de lecture ou d'écriture. Exemple:
schéma.json :
{
"properties" : {
"id" : {
"type" : " number " ,
"readOnly" : true
}
}
}
Extrait de code de validation :
Validator validator = Validator . builder ()
. readWriteContext ( ReadWriteContext . WRITE )
. build ();
validator . performValidation ( schema , new JSONObject ( "{ " id " :42}" ));
Dans ce cas, nous avons indiqué au validateur que la validation se produit dans le contexte WRITE
et que dans l'objet JSON d'entrée, la propriété "id"
apparaît, qui est marquée comme "readOnly"
dans le schéma, donc cet appel lancera une ValidationException
.
À partir de la version 1.2.0
la bibliothèque prend en charge le mot-clé "format"
(qui est une partie facultative de la spécification).
Les formats pris en charge varient en fonction de la version de la spécification de schéma que vous utilisez (puisque les formats standard ont été introduits dans différentes versions de la spécification de validation).
Voici un tableau de compatibilité des formats standards supportés :
Brouillon 4 | Brouillon 6 | Brouillon 7 | |
---|---|---|---|
date-heure | ✅ | ✅ | ✅ |
✅ | ✅ | ✅ | |
nom d'hôte | ✅ | ✅ | ✅ |
IPv4 | ✅ | ✅ | ✅ |
IPv6 | ✅ | ✅ | ✅ |
uri | ✅ | ✅ | ✅ |
référence uri | ✅ | ✅ | |
modèle uri | ✅ | ✅ | |
json-pointeur | ✅ | ✅ | |
date | ✅ | ||
temps | ✅ | ||
expression régulière | ✅ | ||
pointeur-json relatif | ✅ |
La bibliothèque prend également en charge l'ajout de validateurs de format personnalisés. Pour utiliser un validateur personnalisé, vous devez essentiellement
org.everit.json.schema.FormatValidator
org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder
avant de charger le schéma réelSupposons que la tâche consiste à créer un validateur personnalisé qui accepte les chaînes avec un nombre pair de caractères.
Le FormatValidator
personnalisé ressemblera à ceci :
public class EvenCharNumValidator implements FormatValidator {
@ Override
public Optional < String > validate ( final String subject ) {
if ( subject . length () % 2 == 0 ) {
return Optional . empty ();
} else {
return Optional . of ( String . format ( "the length of string [%s] is odd" , subject ));
}
}
}
Pour lier EvenCharNumValidator
à une valeur "format"
(par exemple "evenlength"
), vous devez lier une instance de validateur au mot-clé dans la configuration du chargeur de schéma :
JSONObject rawSchema = new JSONObject ( new JSONTokener ( inputStream ));
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( rawSchema ) // rawSchema is the JSON representation of the schema utilizing the "evenlength" non-standard format
. addFormatValidator ( "evenlength" , new EvenCharNumValidator ()) // the EvenCharNumValidator gets bound to the "evenlength" keyword
. build ();
Schema schema = schemaLoader . load (). build (); // the schema is created using the above created configuration
schema . validate ( jsonDocument ); // the document validation happens here
Dans un document de schéma JSON, il est possible d'utiliser des URI relatifs pour faire référence à des types précédemment définis. Ces références sont exprimées à l'aide des mots-clés "$ref"
et "$id"
. Bien que la spécification décrit en détail la modification de la portée de la résolution et le déréférencement, elle n'explique pas le comportement attendu lorsque le premier "$ref"
ou "$id"
apparaissant est un URI relatif.
Dans le cas de cette implémentation, il est possible de définir explicitement un URI absolu servant d'URI de base (portée de résolution) en utilisant la méthode builder appropriée :
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( jsonSchema )
. resolutionScope ( "http://example.org/" ) // setting the default resolution scope
. build ();
Au fur et à mesure que vos schémas grandissent, vous souhaiterez les diviser en plusieurs fichiers sources et les connecter avec des références "$ref"
. Si vous souhaitez stocker les schémas sur le chemin de classe (au lieu par exemple de les servir via HTTP), la méthode recommandée est d'utiliser le protocole classpath:
pour que les schémas se référencent les uns les autres. Pour faire fonctionner le protocole classpath:
: :
SchemaClient
intégré à la bibliothèque, prenant en compte le chemin de classe, par exemple : SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaClient ( SchemaClient . classPathAwareClient ())
. schemaJson ( jsonSchema )
. resolutionScope ( "classpath://my/schemas/directory/" ) // setting the default resolution scope
. build ();
Compte tenu de cette configuration, les références suivantes seront correctement résolues dans jsonSchema
:
{
"properties" : {
"sameDir" : { "$ref" : " sameDirSchema.json " },
"absPath" : { "$ref" : " classpath://somewhere/else/otherschema.json " },
"httpPath" : { "$ref" : " http://example.org/http-works-as-usual " },
}
}
et sameDirSchema.json
sera recherché dans /my/schemas/directory/sameDirSchema.json
sur le chemin de classe.
Parfois, il est utile de travailler avec des schémas préchargés, auxquels nous attribuons un URI arbitraire (peut-être un uuid) au lieu de charger le schéma via une URL. Cela peut être fait en attribuant les schémas à un URI avec la méthode #registerSchemaByURI()
du chargeur de schéma. Exemple:
SchemaLoader schemaLoader = SchemaLoader . builder ()
. registerSchemaByURI ( new URI ( "urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63" ), aJSONObject )
. registerSchemaByURI ( new URI ( "http://example.org" ), otherJSONObject )
. schemaJson ( jsonSchema )
. resolutionScope ( "classpath://my/schemas/directory/" )
. build ();
Remarques :
JSONObject
ou un Boolean
(le type de paramètre formel est Object
uniquement car ces deux-là n'ont pas d'autre superclasse commune).SchemaClient
fonctionne également, ou vous pouvez même utiliser la gestion de protocole extensible du package java.net
) Certaines dépendances peuvent être exclues de la bibliothèque, et celle-ci reste utilisable, avec quelques limitations :
com.damnhandy:handy-uri-templates
, alors votre schéma ne doit pas utiliser le format "uri-template"
commons-validator:commons-validator
, votre schéma ne doit pas utiliser les formats suivants : "email"
, "ipv4"
, "ipv6"
, "hostname"
Par version de bibliothèque :
Le javadoc généré des versions 1.0.0 - 1.5.1 est disponible sur javadoc.io
Pour les versions intermédiaires (1.6.0 - 1.9.1), il n'est publié nulle part.