My problem is how do I get the json-schema-validator library to actually validate a JSON response against the JSON Schema I have defined. My goal is to get "additionalProperties" and field type checking working. I.e. have the validation reject unknown fields and check that numerics don't contain strings etc.
Files, code and background information follow.
Schema Swagger 2.0 standard definition
Schema Students API Defintion
{
"swagger": "2.0",
"info": {
"title": "Students API",
"description": "Operations on Students",
"version": "1.0"
},
"basePath": "/v1/students",
"schemes": [
"https"
],
"consumes": [
"application/json"
],
"produces": [
"application/json",
"application/xml"
],
"paths": {
"/{UID}": {
"get": {
"tags": [
"students"
],
"summary": "Get information about a Students Member.",
"description": "Given an University ID, return information for the identified students member.",
"operationId": "getStudentsByUID",
"produces": [
"application/xml",
"application/json"
],
"parameters": [
{
"required": true,
"name": "UID",
"in": "path",
"type": "string"
}
],
"responses": {
"200": {
"description": "OK. The operation was successful.",
"schema": {
"$ref": "#/definitions/Students"
}
},
"204": {
"description": "No Content. No Students the UID"
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Not Authorised"
},
"405": {
"description": "Method Not Allowed"
},
"429": {
"description": "Too Many Requests"
},
"500": {
"description": "Internal Server Error"
}
}
}
}
},
"definitions": {
"Students": {
"type": "object",
"additionalProperties": false,
"properties": {
"Status": {
"$ref": "#/definitions/Status"
},
"Person": {
"type": "array",
"items": {
"$ref": "#/definitions/Person"
}
}
}
},
"Status": {
"type": "object",
"additionalProperties": false,
"properties": {
"StatusCode": {
"type": "integer"
},
"Reason": {
"type": "string"
}
},
"required": [
"StatusCode",
"Reason"
]
},
"Person": {
"type": "object",
"description": "Representation of a person record for a student",
"properties": {
"PersonIdentifiers": {
"type": "object",
"properties": {
"Identifier": {
"type": "array",
"items": {
"$ref": "#/definitions/Identifier"
}
}
}
},
"PersonNames": {
"$ref": "#/definitions/PersonNames"
},
"AcademicPlanDetails": {
"type": "object",
"properties": {
"AcademicPlanDetail": {
"type": "array",
"items": {
"$ref": "#/definitions/AcademicPlanDetail"
}
}
}
}
}
},
"Identifier": {
"type": "object",
"properties": {
"IdentifierType": {
"type": "string"
},
"IdentifierValue": {
"type": "string"
}
}
},
"PersonNames": {
"type": "object",
"properties": {
"FirstName": {
"type": "string"
},
"MiddleName": {
"type": "string"
},
"LastName": {
"type": "string"
},
"Title": {
"type": "string",
"enum": [
"Mr",
"Mrs",
"Ms",
"Master",
"Dr"
]
},
"NameSuffixes": {
"description": "Not applicable to all Persons",
"type": "string",
"enum": [
"Jr",
"CPA",
"Esq",
"Ph.D"
]
},
"FullName": {
"type": "string"
},
"PreferredName": {
"description": "Not applicable to all Persons",
"type": "string"
},
"LongName": {
"type": "string"
},
"AliasName": {
"description": "Not applicable to all Persons",
"type": "string"
},
"Pronunciation": {
"description": "Not applicable to all Persons",
"type": "string"
}
}
},
"AcademicPlanDetail": {
"type": "object",
"properties": {
"AcademicCareer": {
"type": "string"
},
"StudentCareerNumber": {
"type": "string"
},
"EffectiveDate": {
"type": "string"
},
"EffectiveSequence": {
"type": "string"
},
"AcademicProgram": {
"type": "string"
},
"AcademicProgramDescription": {
"type": "string"
},
"AcademicPlan": {
"type": "string"
},
"AcademicPlanDescription": {
"type": "string"
},
"AcademicSubPlan": {
"type": "string"
},
"AcademicSubPlanDescription": {
"type": "string"
},
"ProgramStatus": {
"type": "string"
},
"ProgramStatusDescription": {
"type": "string"
},
"ProgramAction": {
"type": "string"
},
"ProgramActionDescription": {
"type": "string"
},
"ProgramReason": {
"type": "string"
},
"ProgramReasonDescription": {
"type": "string"
},
"AdmittedTerm": {
"type": "string"
},
"ResearchStudentCandidateNumber": {
"type": "string"
}
}
}
},
"tags": [
{
"name": "api-metadata",
"description": "Information about the API including its terms of use"
},
{
"name": "students",
"description": "Operations on all types of University students"
}
]
}
Students Response Good Valid response message
{
"Status": {
"StatusCode": 200,
"Reason": "OK",
},
"Person": [
{
"PersonIdentifiers": {
"Identifier": [
{
"IdentifierType": "UniversityID",
"IdentifierValue": "12345678"
},
{
"IdentifierType": "EmailAddress",
"IdentifierValue": "JohnSmith#no.such.org"
},
{
"IdentifierType": "PositionNumber",
"IdentifierValue": "00006789"
}
]
},
"PersonNames": {
"PreferredName": "",
"FirstName": "Johnathon",
"MiddleName": "",
"LastName": "Smith",
"Title": "Dr",
"NameSuffixes": "",
"FullName": "Smith,Jonathon",
"LongName": "",
"AliasName": "",
"Pronunciation": ""
},
"AcademicPlanDetails": {
"AcademicPlanDetail": [
{
"AcademicCareer": "RSCH",
"StudentCareerNumber": "0",
"EffectiveDate": "2019-12-19",
"EffectiveSequence": "1",
"AcademicProgram": "9876",
"AcademicProgramDescription": "Arts & Social Sciences",
"AcademicPlan": "9876XPHD",
"AcademicPlanDescription": "School of Humanities and the Arts",
"AcademicSubPlan": "HUMAN",
"AcademicSubPlanDescription": "Humanities",
"ProgramStatus": "AC",
"ProgramStatusDescription": "Active",
"ProgramAction": "MATR",
"ProgramActionDescription": "Matriculate",
"ProgramReason": "ACTV",
"ProgramReasonDescription": "Activated Manually",
"AdmittedTerm": "1903",
"ResearchStudentCandidateNumber": "000000034567"
}
]
}
}
]
}
Students Response Bad Invalid response message. Includes unwanted aggregates, fields and wrong data types.
{
"Fred": {
"Freddy": "Kruger"
},
"Status": {
"StatusCode": "200",
"Reason": "OK",
"X-Event-Id": "12340818-5a7c-11ea-920e-0a2835080000-1",
"Unexpected": "Undefined additional propery"
},
"Person": [
{
"PersonIdentifiers": {
"Identifier": [
{
"IdentifierType": "UniversityID",
"IdentifierValue": "12345678"
},
{
"IdentifierType": "EmailAddress",
"IdentifierValue": "JohnSmith#no.such.org"
},
{
"IdentifierType": "PositionNumber",
"IdentifierValue": "00006789"
}
]
},
"PersonNames": {
"PreferredName": "",
"FirstName": "Johnathon",
"MiddleName": "",
"LastName": "Smith",
"Title": "Dr",
"NameSuffixes": "",
"FullName": "Smith,Jonathon",
"LongName": "",
"AliasName": "",
"Pronunciation": ""
},
"AcademicPlanDetails": {
"AcademicPlanDetail": [
{
"AcademicCareer": "RSCH",
"StudentCareerNumber": "0",
"EffectiveDate": "2019-12-19",
"EffectiveSequence": "1",
"AcademicProgram": "9876",
"AcademicProgramDescription": "Arts & Social Sciences",
"AcademicPlan": "9876XPHD",
"AcademicPlanDescription": "School of Humanities and the Arts",
"AcademicSubPlan": "HUMAN",
"AcademicSubPlanDescription": "Humanities",
"ProgramStatus": "AC",
"ProgramStatusDescription": "Active",
"ProgramAction": "MATR",
"ProgramActionDescription": "Matriculate",
"ProgramReason": "ACTV",
"ProgramReasonDescription": "Activated Manually",
"AdmittedTerm": "1903",
"ResearchStudentCandidateNumber": "000000034567"
}
]
}
}
]
}
Code JSON Schema and JSON Instance validation
package com.nsd;
import java.io.File;
import java.io.IOException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.fge.jackson.JsonLoader;
import com.github.fge.jsonschema.core.exceptions.ProcessingException;
import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchema;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
public class JsvCmd {
public static final String JSON_V4_SCHEMA_IDENTIFIER = "http://json-schema.org/draft-04/schema#";
public static final String JSON_SCHEMA_IDENTIFIER_ELEMENT = "$schema";
public static void main(String[] args) throws IOException, ProcessingException {
final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
// Load the JSON Schema for Swagger 2.0 API definition
File swaggerSchemaFile = new File("/Users/syntegrity/Documents/AnuStuff/Swagger2.0.json");
JsonNode jsonNodeSwagger = JsonLoader.fromFile(swaggerSchemaFile);
JsonNode schemaIdentifierSwagger = jsonNodeSwagger.get(JSON_SCHEMA_IDENTIFIER_ELEMENT);
if (null != schemaIdentifierSwagger) {
System.out.println("Swagger Schema Type: " + schemaIdentifierSwagger.asText());
}
final JsonSchema jsonSchemaSwagger = factory.getJsonSchema(jsonNodeSwagger);
// Next load the schema we are interested in and validate it against the Swagger 2.0 API schema
File userSchemaFile = new File("/Users/syntegrity/Documents/AnuStuff/Students_V1_SO.json");
JsonNode jsonNodeUserSchema = JsonLoader.fromFile(userSchemaFile);
ProcessingReport userSchemaReport = jsonSchemaSwagger.validate(jsonNodeUserSchema, true);
if (!userSchemaReport.isSuccess()) {
System.out.println("UserSchema is invalid");
System.out.println(userSchemaReport);
return;
}
// To get the validators we want used happening add a $schema element that specifies IETF Draft-04
JsonNode schemaIdentifierUser = jsonNodeUserSchema.get(JSON_SCHEMA_IDENTIFIER_ELEMENT);
if (null == schemaIdentifierUser) {
((ObjectNode) jsonNodeUserSchema).put(JSON_SCHEMA_IDENTIFIER_ELEMENT, JSON_V4_SCHEMA_IDENTIFIER);
System.out.println("User Schema coerced to IETF Draft-04 for validation purposes");
} else {
// For what we want to do anything other than a $schema value of IETF Draft-04 is going
// to cause problems. NOTE: The Swagger 2.0 API schema does not allow $schema anyway
if (!JSON_V4_SCHEMA_IDENTIFIER.equals(schemaIdentifierUser.asText())) {
System.out.println("Schema Type: " + schemaIdentifierUser.asText() + " is not " + JSON_V4_SCHEMA_IDENTIFIER);
return;
}
}
JsonSchema jsonSchemaUser = factory.getJsonSchema(jsonNodeUserSchema);
// Finally validate the JSON data
File jsonDataFile = new File("/Users/syntegrity/Documents/AnuStuff/Students_Bad_SO.json");
JsonNode jsonNodeData = JsonLoader.fromFile(jsonDataFile);
ProcessingReport userDataReport = jsonSchemaUser.validate(jsonNodeData, true);
if (!userSchemaReport.isSuccess()) {
System.out.println(userDataReport);
} else {
System.out.println("Sweet");
}
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nsd</groupId>
<artifactId>jsvcmd</artifactId>
<version>1.0.0</version>
<name>JsonValidator</name>
<description>JSON Validator Command Line</description>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.atlassian.oai/swagger-request-validator -->
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>jackson-coreutils</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Run Results
Swagger Schema Type: http://json-schema.org/draft-04/schema#
User Schema coerced to IETF Draft-04 for validation purposes
Sweet
I was hoping to see the following errors rather than "Sweet"
Unwanted: [Fred]
Unwanted: [X-Event-Id]
Unwanted: [Unexpected]
"200" is not of type integer
Debugging
If I change "produces" to "producesx" in the Students Schema the I get the following output.
UserSchema is invalid
com.github.fge.jsonschema.core.report.ListProcessingReport: failure
--- BEGIN MESSAGES ---
error: object instance has properties which are not allowed by the schema: ["producesx"]
level: "error"
schema: {"loadingURI":"#","pointer":""}
instance: {"pointer":""}
domain: "validation"
keyword: "additionalProperties"
unwanted: ["producesx"]
--- END MESSAGES ---
So when I use the Swagger 2.0 Schema to validate my Students Schema after making it invalid I get an error message. Yay.
This is not the case when I try to use Students Schema to validate Students Response Bad.
After getting very frustrated I finally decide to use Java Debug to step into the json-schema-validator and json-schema-core libraries.
In the InstanceValidator.class, see screenshots below, I found that for the first jsonSchemaSwagger.validate(jsonNodeUserSchema, true); call there were three validators linked to the fullContext object whereas in the second jsonSchemaUser.validate(jsonNodeData, true); call there were no validators linked to the fullContext object. Which I'm pretty sure means that no validation will take place.
First Call
InstanceValidator.class
Three Validators AdditionalPropertiesValidator, RequiredKeywordValidator and DraftV4TypeValidator
Second Call
No Validators
Finally
And so finally to the question. What calls do I need to make to use the same set of Validators for my second validation call? I.e. how do a create a list of Validators and then get them executed against the JSON Instance data?
enter image description hereI have a json schema looks like below,and I want load the definitions into D and E based on the values of B and C for that I've written allOf conditioning.
and i'm using json-schema-validator for json schema validation in application.
i)the below schema always passing as valid because the allOf condition never evaluated and it's not
loading validators properties like maxLenth,multipleOf from the definitions.
ii)I was suspecting I did the conditioning in a wrong place(the root schema or sub schema) and i tried
moving this allof logic to subschema level(inside the B,C and D,E)
iii)I've tried executing the allOf example mentioned on https://json-schema.org/understanding-json-schema/reference/conditionals.html it is also passing as valid. for this I did verified on a online josn schema validator http://json-schema-validator.herokuapp.com/ which is also using same library json-schema-validator.
iv)is there any ValidationConfiguration requires for JsonSchemaFactory to validate the Draft7 jsonSchema conditioning since the Defaultlibrary is DRAFT-4 on this json-schema-validator.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": [
"A",
"B",
"C",
"D",
"E"
],
"properties": {
"A": {
"type": "string"
},
"B": {
"type": "string",
"enum": ["TEST1","TEST2"]
},
"C": {
"type": "string",
"enum": ["TEST3","TEST4"]
},
"D": {
"type": "object"
},
"E": {
"type": "object"
}
},
"allOf": [
{
"if": {
"properties": { "B": { "const": "TEST1" } }
},
"then": {
"properties": { "D": { "$ref": "#/definitions/test" } }
}
},
{
"if": {
"properties": { "B": { "const": "TEST2" } }
},
"then": {
"properties": { "D": { "$ref": "#/definitions/testTwo" } }
}
},
{
"if": {
"properties": { "C": { "const": "TEST3" } }
},
"then": {
"properties": { "E": { "$ref": "#/definitions/testThree" } }
}
},
{
"if": {
"properties": { "C": { "const": "TEST4" } }
},
"then": {
"properties": { "E": { "$ref": "#/definitions/test4" } }
}
}
],
"definitions": {
"testOne":{"type":"object"},
"testTwo":{"type":"object"},
"testThree":{"type":"object"},
"testFour":{"type":"object"}
}
}
And the javaCode looks like
#PostMapping("/sendMessage")
public ProcessingReport sendMessage(#RequestBody SampleRequest request) throws IOException, ProcessingException {
//step-1 writing request object into String
String requestJson = objectMapper.writeValueAsString(request);
//Step-2 getting jsonNode for requested json
JsonNode dataNode = JsonLoader.fromString(requestJson);
//step -3 creating jsonSchema factory(default)
JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
//validating requested jsonNode(dataNode) against SchemaNode(schema of request json,which is loaded from resources)
ProcessingReport report = factory.getJsonSchema(schemaNode).validate(dataNode);
//Processing report resulting the given json validation is successful or not
if(!report.isSuccess()) {
System.out.println(report);
}
return report;
}
json-schema-validator only supports draft-03 and draft-04. if/then/const were added in later drafts. Those keywords get ignored resulting in the no-op behavior you're experiencing.
You have two choices
pick a different implementation that supports draft-07
Use the Implication Pattern instead. It's a little more verbose, but the result is the same.
I have following java Object:
ProcessBean.java
import java.util.List;
import com.fasterxml.jackson.annotation.JsonRootName;
#JsonRootName(value = "process")
public class ProcessBean{
private Integer id;
private String name;
private String description;
private String user;
private String executePermissions;
private String createdDtm;
private String updatedDtm;
private Process tpProcess;
private List<ProcessParamBean> processParameters;
/* --------Getters and Setters ----------*/
}
I need to find the JSON schema for this object which is used to display these fields on UI. The order of fields in UI is determined by the order of properties in JSON schema generated.
I have generated the schema using following code:
DataSpec.java
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper;
public class DataSpec {
public static <T> String getDataSpec(Class<T> clazz) {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
MetauiVisitorContext obj = new MetauiVisitorContext();
visitor.setVisitorContext(obj);
try {
mapper.acceptJsonFormatVisitor(clazz, visitor);
JsonSchema schema = visitor.finalSchema();
return mapper.writeValueAsString(schema);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
MetauiVisitorContext.java
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.module.jsonSchema.factories.VisitorContext;
public class MetauiVisitorContext extends VisitorContext{
#Override
public String getSeenSchemaUri(JavaType aSeenSchema) {
return null;
}
}
The resultant schema looks like below:
{
"type": "object",
"id": "urn:jsonschema:com:restservices:api:jsonbeans:ProcessBean",
"properties": {
"updatedDtm": {
"type": "string"
},
"createdDtm": {
"type": "string"
},
"name": {
"type": "string"
},
"tpProcess": {
"type": "object",
"id": "urn:jsonschema:com:restservices:api:jsonbeans:Process",
"properties": {
"name": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"description": {
"type": "string"
},
"id": {
"type": "integer"
},
"processParameters": {
"type": "array",
"items": {
"type": "object",
"id": "urn:jsonschema:com:restservices:api:jsonbeans:ProcessParamBean",
"properties": {
"updatedDtm": {
"type": "string"
},
"defaultValue": {
"type": "string"
},
"createdDtm": {
"type": "string"
},
"masterParam": {
"type": "object",
"id": "urn:jsonschema:com:restservices:api:jsonbeans:MasterParamBean",
"properties": {
"updatedDtm": {
"type": "string"
},
"createdDtm": {
"type": "string"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"id": {
"type": "integer"
}
}
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"processParamId": {
"type": "integer"
},
"id": {
"type": "integer"
},
"userPrompted": {
"type": "boolean"
},
"tags": {
"type": "string"
}
}
}
},
"executePermissions": {
"type": "string"
},
"user": {
"type": "string"
}
}
}
As it can be seen that the order of properties in JSON schema does not match the order of fields defined in Java object which is necessary in my case.
So how can I determine the sequence of the properties?
There is not an intrinsic order of properties in a JSON schema object, as it happens with a regular JSON or javascript object. Indeed, it does not change the validation semantics. The following schemas validate exactly the same collection of objects:
Schema 1:
{
"properties" : {
"prop1" : {
"type" : "string"
},
"prop2" : {
"type" : "number"
}
}
}
Schema 2:
{
"properties" : {
"prop2" : {
"type" : "number"
},
"prop1" : {
"type" : "string"
}
}
}
If you want to preserve some ordering you need to do it within and array. You could achieve this by using an array of objects instead of an object in the items clause. An example:
{
"type" : "array" :
"items" : [{
"properties" : {
"prop2" : {
"type" : "number"
}
}
}, {
"properties" : {
"prop1" : {
"type" : "string"
}
}
}
]
}
This way you get an ordered definition of UI items. Unfortunately, it forces you to nest each single property in an object.
Order is out of scope for json-schema. For that you need to look into json-forms https://jsonforms.io/, standards, which is extension of json-schema. It consist of three separate JSON data :
schema
values
ui-schema
The ui-schema contains the information about the ui and presentation, like order of properties (fields) and the their visual presentation, like "hidden", "read-only".