Esta biblioteca se encuentra actualmente en modo de mantenimiento y fue reemplazada por erosb/json-sKema.
Este repositorio no verá ninguna característica nueva. Proporciona soporte sólido para las versiones draft-04, draft-06 y draft-07 de la especificación del esquema JSON.
El último borrador 2020-12 solo es compatible con erosb/json-sKema.
Este proyecto es una implementación de las especificaciones JSON Schema Draft v4, Draft v6 y Draft v7. Utiliza la API org.json (creada por Douglas Crockford) para representar datos JSON.
Supongamos que ya sabe qué es el esquema JSON y desea utilizarlo en una aplicación Java para validar datos JSON. Pero, como ya habrás descubierto, también existe otra implementación Java de la especificación del esquema JSON. Así que aquí hay algunos consejos sobre cuál usar:
Agregue la siguiente dependencia a su pom.xml
:
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema</ artifactId >
< version >1.14.4</ version >
</ dependency >
Nota sobre versiones anteriores : las versiones entre 1.6.0
y 1.9.1
solo se pueden encontrar en JitPack con coordenadas com.github.everit-org.json-schema:org.everit.json.schema
. Las versiones 1.0.0
... 1.5.1
están disponibles en Maven Central en las coordenadas org.everit.json:org.everit.json.schema
.
Hubo un par de intentos para que la biblioteca funcionara en Java 6/7.
@mindbender1 desarrolló un puerto java6 de la versión 1.9.2 y se puede acceder a él a través de Maven Central con las siguientes coordenadas:
< dependency >
< groupId >com.github.erosb</ groupId >
< artifactId >everit-json-schema-jdk6</ artifactId >
< version >1.9.2</ version >
</ dependency >
Backports de versiones anteriores:
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 tiene actualmente 4 versiones principales, Draft 3, Draft 4, Draft 6 y Draft 7. Esta biblioteca implementa las 3 más nuevas, puede echar un vistazo rápido a las diferencias aquí y aquí. Dado que las dos versiones tienen varias diferencias (y el borrador 6 no es compatible con el borrador 4), es bueno saber qué versión usará.
La mejor manera de indicar la versión del esquema JSON que desea utilizar es incluir la URL del metaesquema en la raíz del documento con la clave "$schema"
. Esta es una notación común, facilitada por la biblioteca para determinar qué versión se debe utilizar.
Referencia rápida:
"$schema": "http://json-schema.org/draft-04/schema"
en la raíz del esquema, entonces se utilizará el Borrador 4"$schema": "http://json-schema.org/draft-06/schema"
en la raíz del esquema, entonces se utilizará el Borrador 6"$schema": "http://json-schema.org/draft-07/schema"
en la raíz del esquema, entonces se utilizará el Borrador 7Si desea especificar explícitamente la versión del metaesquema, puede cambiar el valor predeterminado del Borrador 4 al Borrador 6/7 configurando el cargador de esta manera:
SchemaLoader loader = SchemaLoader . builder ()
. schemaJson ( yourSchemaJSON )
. draftV6Support () // or draftV7Support()
. build ();
Schema schema = loader . load (). build ();
A partir de la versión 1.1.0
el validador recopila todas las violaciones del esquema (en lugar de fallar inmediatamente en la primera). Cada error se indica mediante un puntero JSON, que apunta desde la raíz del documento a la parte infractora. Si se han detectado más de una infracción de esquema, se generará una ValidationException
en los elementos principales más comunes de las infracciones, y cada infracción por separado se puede obtener utilizando el método ValidationException#getCausingExceptions()
.
Para demostrar los conceptos anteriores, veamos un ejemplo. Consideremos el siguiente esquema:
{
"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 " }
}
}
}
}
El siguiente documento JSON tiene solo una infracción del esquema (ya que "a" no puede ser negativa):
{
"rectangle" : {
"a" : -5 ,
"b" : 5
}
}
En este caso, la ValidationException
lanzada apuntará a #/rectangle/a
y no contendrá subexcepciones:
try {
schema . validate ( rectangleSingleFailure );
} catch ( ValidationException e ) {
// prints #/rectangle/a: -5.0 is not higher or equal to 0
System . out . println ( e . getMessage ());
}
Ahora, para ilustrar la forma en que se manejan las infracciones múltiples, consideremos el siguiente documento JSON, donde las propiedades "a" y "b" violan el esquema anterior:
{
"rectangle" : {
"a" : -5 ,
"b" : " asd "
}
}
En este caso, la ValidationException
lanzada apuntará a #/rectangle
y tiene 2 subexcepciones, que apuntan a #/rectangle/a
y #/rectangle/b
:
try {
schema . validate ( rectangleMultipleFailures );
} catch ( ValidationException e ) {
System . out . println ( e . getMessage ());
e . getCausingExceptions (). stream ()
. map ( ValidationException :: getMessage )
. forEach ( System . out :: println );
}
Esto imprimirá el siguiente resultado:
#/rectangle: 2 schema violations found
#/rectangle/a: -5.0 is not higher or equal to 0
#/rectangle/b: expected type: Number, found: String
Desde la versión 1.4.0
es posible imprimir las instancias ValidationException
como informes de fallas en formato JSON. El método ValidationException#toJSON()
devuelve una instancia JSONObject
con las siguientes claves:
"message"
: el mensaje de excepción fácil de usar para el programador (descripción del error de validación)"keyword"
: la palabra clave del esquema JSON que fue violada"pointerToViolation"
: un puntero JSON que indica la ruta desde la raíz del documento de entrada hasta su fragmento que provocó el error de validación."schemaLocation"
: un puntero JSON que indica la ruta desde la raíz JSON del esquema hasta la palabra clave violada."causingExceptions"
: una matriz (posiblemente vacía) de subexcepciones. Cada subexcepción se representa como un objeto JSON, con la misma estructura que se describe en este listado. Vea más arriba sobre cómo causar excepciones. Tenga en cuenta que el informe de fallo completo es una estructura de árbol jerárquica : las subcausas de una causa se pueden obtener utilizando #getCausingExceptions()
.
ValidationListener
s puede servir para resolver la ambigüedad sobre cómo una instancia JSON coincide (o no coincide) con un esquema. Puede adjuntar una implementación ValidationListener
al validador para recibir notificaciones de eventos sobre resultados intermedios de éxito o fracaso.
Ejemplo:
import org . everit . json . schema . Validator ;
...
Validator validator = Validator . builder ()
. withListener ( new YourValidationListenerImplementation ())
. build ();
validator . performValidation ( schema , input );
Los eventos actualmente admitidos:
"$ref"
"allOf"
/ "anyOf"
/ "oneOf"
"allOf"
/ "anyOf"
/ "oneOf"
que no coincide"if"
"if"
no coincide"then"
"then"
que no coincide"else"
"else"
que no coincide Consulte el javadoc de la interfaz org.everit.json.schema.event.ValidationListener
para obtener más detalles. Las clases de eventos particulares también tienen implementaciones #toJSON()
y #toString()
adecuadas para que pueda imprimirlas en un formato fácilmente analizable.
De forma predeterminada, el error de validación informa en el modo de recopilación (consulte el capítulo "Investigación de fallas"). Esto es conveniente para tener un informe de errores detallado, pero en algunas circunstancias es más apropiado detener la validación cuando se encuentra una falla sin verificar el resto del documento JSON. Para alternar este modo de validación que falla rápidamente
Validator
para su esquema en lugar de llamar Schema#validate(input)
failEarly()
de ValidatorBuilder
Ejemplo:
import org . everit . json . schema . Validator ;
...
Validator validator = Validator . builder ()
. failEarly ()
. build ();
validator . performValidation ( schema , input );
Nota: la clase Validator
es inmutable y segura para subprocesos, por lo que no es necesario crear una nueva para cada validación, basta con configurarla solo una vez.
En algunos casos, al validar números o valores booleanos, tiene sentido aceptar valores de cadena que sean analizables como tales primitivos, porque cualquier procesamiento sucesivo también analizará automáticamente estos literales en valores numéricos y lógicos adecuados. Además, los valores primitivos que no son cadenas son triviales de convertir en cadenas, entonces, ¿por qué no permitir primitivas json como cadenas?
Por ejemplo, tomemos este esquema:
{
"properties" : {
"booleanProp" : {
"type" : " boolean "
},
"integerProp" : {
"type" : " integer "
},
"nullProp" : {
"type" : " null "
},
"numberProp" : {
"type" : " number "
},
"stringProp" : {
"type" : " string "
}
}
}
El siguiente documento JSON no se puede validar, aunque todas las cadenas podrían convertirse fácilmente en valores apropiados:
{
"numberProp" : " 12.34 " ,
"integerProp" : " 12 " ,
"booleanProp" : " true " ,
"nullProp" : " null " ,
"stringProp" : 12.34
}
En este caso, si desea que la instancia anterior pase la validación según el esquema, debe utilizar la configuración de validación primitiva indulgente activada. Ejemplo:
import org . everit . json . schema .*;
...
Validator validator = Validator . builder ()
. primitiveValidationStrategry ( PrimitiveValidationStrategy . LENIENT )
. build ();
validator . performValidation ( schema , input );
Nota: en el modo de análisis indulgente, los 22 literales booleanos posibles se aceptarán como valores lógicos.
La especificación del esquema JSON define la palabra clave "predeterminada" para indicar valores predeterminados, aunque no establece explícitamente cómo debería afectar el proceso de validación. De forma predeterminada, esta biblioteca no establece los valores predeterminados, pero si necesita esta característica, puede activarla mediante el método SchemaLoaderBuilder#useDefaults(boolean)
, antes de cargar el esquema:
{
"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
Si faltan algunas propiedades en input
que tienen valores "default"
en el esquema, el validador las establecerá durante la validación.
Para admitir la palabra clave "regex"
del esquema JSON, la biblioteca ofrece dos implementaciones posibles:
java.util.regex
Si bien la biblioteca RE2J proporciona un rendimiento significativamente mejor que java.util.regex
, no es completamente compatible con la sintaxis admitida por java.util
o ECMA 262. Por lo tanto, se recomienda RE2J si le preocupa el rendimiento y sus limitaciones son aceptables.
La implementación de RE2J se puede activar con la llamada SchemaLoaderBuilder#regexpFactory()
:
SchemaLoader loader = SchemaLoader . builder ()
. regexpFactory ( new RE2JRegexpFactory ())
// ...
. build ();
Notas:
pom.xml
para que no aumente innecesariamente el tamaño de su artefacto.java.util
, en la 1.8.0 se usó la implementación RE2J, y en la 1.9.0 la hicimos configurable, debido a algunas regresiones reportadas. La biblioteca admite las palabras clave readOnly
y writeOnly
que aparecieron por primera vez en el Borrador 7. Si desea utilizar esta función, antes de la validación debe informarle al validador si la validación se realiza en contexto de lectura o escritura. Ejemplo:
esquema.json:
{
"properties" : {
"id" : {
"type" : " number " ,
"readOnly" : true
}
}
}
Fragmento de código de validación:
Validator validator = Validator . builder ()
. readWriteContext ( ReadWriteContext . WRITE )
. build ();
validator . performValidation ( schema , new JSONObject ( "{ " id " :42}" ));
En este caso le dijimos al validador que la validación ocurre en contexto WRITE
, y en el objeto JSON de entrada aparece la propiedad "id"
, que está marcada como "readOnly"
en el esquema, por lo tanto esta llamada arrojará una ValidationException
.
A partir de la versión 1.2.0
la biblioteca admite la palabra clave "format"
(que es una parte opcional de la especificación).
Los formatos admitidos varían según la versión de especificación de esquema que utilice (ya que los formatos estándar se introdujeron en diferentes versiones en la especificación de validación).
Aquí hay una tabla de compatibilidad de los formatos estándar admitidos:
Borrador 4 | Borrador 6 | Borrador 7 | |
---|---|---|---|
fecha-hora | ✅ | ✅ | ✅ |
correo electrónico | ✅ | ✅ | ✅ |
nombre de host | ✅ | ✅ | ✅ |
ipv4 | ✅ | ✅ | ✅ |
ipv6 | ✅ | ✅ | ✅ |
uri | ✅ | ✅ | ✅ |
referencia-uri | ✅ | ✅ | |
plantilla-uri | ✅ | ✅ | |
puntero json | ✅ | ✅ | |
fecha | ✅ | ||
tiempo | ✅ | ||
expresión regular | ✅ | ||
puntero-json relativo | ✅ |
La biblioteca también admite la adición de validadores de formato personalizados. Para utilizar un validador personalizado básicamente tienes que
org.everit.json.schema.FormatValidator
org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder
antes de cargar el esquema realSupongamos que la tarea es crear un validador personalizado que acepte cadenas con un número par de caracteres.
El FormatValidator
personalizado se verá así:
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 ));
}
}
}
Para vincular EvenCharNumValidator
a un valor "format"
(por ejemplo, "evenlength"
), debe vincular una instancia del validador a la palabra clave en la configuración del cargador de esquemas:
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
En un documento de esquema JSON es posible utilizar URI relativos para hacer referencia a tipos previamente definidos. Dichas referencias se expresan utilizando las palabras clave "$ref"
y "$id"
. Si bien la especificación describe en detalle la alteración del alcance de la resolución y la desreferenciación, no explica el comportamiento esperado cuando el primer "$ref"
o "$id"
que aparece es un URI relativo.
En el caso de esta implementación, es posible definir explícitamente un URI absoluto que sirva como URI base (alcance de resolución) utilizando el método de creación apropiado:
SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaJson ( jsonSchema )
. resolutionScope ( "http://example.org/" ) // setting the default resolution scope
. build ();
A medida que sus esquemas crezcan, querrá dividirlos en varios archivos fuente y conectarlos con referencias "$ref"
. Si desea almacenar los esquemas en el classpath (en lugar de, por ejemplo, servirlos a través de HTTP), entonces la forma recomendada es utilizar el protocolo classpath:
para hacer que los esquemas hagan referencia entre sí. Para hacer que el protocolo classpath:
funcione:
SchemaClient
integrado en la biblioteca, compatible con classpath, por ejemplo: SchemaLoader schemaLoader = SchemaLoader . builder ()
. schemaClient ( SchemaClient . classPathAwareClient ())
. schemaJson ( jsonSchema )
. resolutionScope ( "classpath://my/schemas/directory/" ) // setting the default resolution scope
. build ();
Dada esta configuración, las siguientes referencias se resolverán adecuadamente en jsonSchema
:
{
"properties" : {
"sameDir" : { "$ref" : " sameDirSchema.json " },
"absPath" : { "$ref" : " classpath://somewhere/else/otherschema.json " },
"httpPath" : { "$ref" : " http://example.org/http-works-as-usual " },
}
}
y se buscará sameDirSchema.json
en /my/schemas/directory/sameDirSchema.json
en el classpath.
A veces resulta útil trabajar con esquemas precargados, a los que asignamos una URI arbitraria (tal vez un uuid) en lugar de cargar el esquema a través de una URL. Esto se puede hacer asignando los esquemas a un URI con el método #registerSchemaByURI()
del cargador de esquemas. Ejemplo:
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 ();
Notas:
JSONObject
o un Boolean
(el tipo de parámetro formal es Object
solo porque estos dos no tienen ninguna otra superclase común).SchemaClient
, o incluso puede utilizar el manejo de protocolo extensible del paquete java.net
) Algunas de las dependencias se pueden excluir de la biblioteca y ésta sigue siendo utilizable, con algunas limitaciones:
com.damnhandy:handy-uri-templates
, entonces su esquema no debería usar el formato "uri-template"
commons-validator:commons-validator
, entonces su esquema no debería usar los siguientes formatos: "email"
, "ipv4"
, "ipv6"
, "hostname"
Por versión de biblioteca:
El javadoc generado de las versiones 1.0.0 - 1.5.1 está disponible en javadoc.io
Para las versiones intermedias (1.6.0 - 1.9.1) no está publicado en ninguna parte.