Skip to content

Spring Boot With Json Schema Validator#

What Is The Json Schema?#

  • JSON Schema is a declarative language that allows you to annotate and validate JSON documents.
  • JSON Schema enables the confident and reliable use of the JSON data format.
  • JSON Schema can help us to:

    • Describes your existing data format(s).
    • Provides clear human- and machine- readable documentation.
    • Validates data which is useful for:
      • Automated testing.
      • Ensuring quality of client submitted data.
  • More Information

  • A JSON Schema document is itself a JSON document that specifies the rules for validating another JSON document. It includes properties that define the schema's structure and validation rules, such as the data types allowed for each property, the minimum and maximum values allowed, and the pattern of characters allowed for string values.

  • JSON Schema can be used to validate JSON data against a specific schema, ensuring that the data conforms to the expected structure and constraints. It can be used to validate data in a variety of contexts, including APIs, databases, and data exchange formats.

Json Schema Versions#

  • JSON Schema is a specification that defines a way to describe the structure and validation constraints for JSON (JavaScript Object Notation) documents. It is versioned like any other software specification, with each version introducing new features and changes.

  • Previous versions of JSON Schema include draft-07, draft-06, draft-04, and draft-03, each with its own set of features and changes. While these older versions are still in use in some contexts, they are gradually being replaced by the newer draft-2020-12 version.

Json Schema Syntax#

  • Base on the Json Schema Version that we are using, then we will have more or less syntax. Let's start with draft-04 and draft-07 because the draft-04 is quite old but it is still using in many legacy system. draft-07 is a relatively minor update from draft-06 and it is fully backwards-compatible with draft-06.

Json Schema Draft-04#

  • Below is the table of syntax that are supported in Json Schema Version draft-04.
Syntax Description
$schema Specifies the version of the JSON Schema specification being used.
id Defines a unique identifier for a schema.
$id Defines a URI-based identifier for a schema.
type Specifies the data type of a property.
properties Defines the set of properties that an object can have, along with their expected types.
items Defines the expected type of items in an array.
required Specifies the list of required properties for an object.
minimum Defines the minimum value allowed for a numeric property.
maximum Defines the maximum value allowed for a numeric property.
exclusiveMinimum Specifies whether the minimum value is included in the range of allowed values.
exclusiveMaximum Specifies whether the maximum value is included in the range of allowed values.
minLength Defines the minimum length of a string property.
maxLength Defines the maximum length of a string property.
pattern Defines a regular expression pattern that a string property must match.
enum Specifies a list of allowed values for a property.
format Specifies the expected format for a property, such as a date or time.
additionalProperties Specifies whether additional properties are allowed for an object.
dependencies Defines the dependencies between properties in an object.
allOf, anyOf, oneOf, not Define logical operators that can be used to combine multiple schemas.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "$id": "https://example.com/schema.json",
  "title": "Example",
  "description": "This is an example schema.",
  "type": "object",
  "properties": {
    "stringProp": {
      "type": "string",
      "default": "default value",
      "minLength": 1,
      "maxLength": 100,
      "pattern": "^[a-zA-Z0-9]*$"
    },
    "numberProp": {
      "type": "number",
      "minimum": 0,
      "maximum": 100,
      "exclusiveMinimum": true,
      "exclusiveMaximum": true
    },
    "integerProp": {
      "type": "integer",
      "minimum": -100,
      "maximum": 100,
      "multipleOf": 2
    },
    "booleanProp": {
      "type": "boolean"
    },
    "objectProp": {
      "type": "object",
      "properties": {
        "innerStringProp": {
          "type": "string",
          "minLength": 1
        },
        "innerNumberProp": {
          "type": "number"
        }
      },
      "required": ["innerStringProp"]
    },
    "arrayProp": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "maxItems": 5,
      "uniqueItems": true
    },
    "nullProp": {
      "type": "null"
    }
  },
  "required": ["stringProp", "numberProp", "integerProp", "booleanProp", "objectProp", "arrayProp", "nullProp"]
}
  • With the example Json Schema above, we will have the valid json as below.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "stringProp": "test",
  "numberProp": 50,
  "integerProp": 4,
  "booleanProp": true,
  "objectProp": {
    "innerStringProp": "inner test",
    "innerNumberProp": 10
  },
  "arrayProp": ["one", "two", "three"],
  "nullProp": null
}

Json Schema Draft-07#

  • Below is the table of syntaxes that are supported in Json Schema Version draft-07.
Syntax Description
$schema Specifies the version of the JSON Schema specification being used.
$id Defines a URI-based identifier for a schema.
title Defines a human-readable title for a schema.
description Provides a detailed description of a schema.
default Specifies a default value for a property.
type Specifies the data type of a property.
enum Specifies a list of allowed values for a property.
const Specifies a single allowed value for a property.
multipleOf Defines a number that a numeric property must be a multiple of.
maximum Defines the maximum value allowed for a numeric property.
exclusiveMaximum Specifies whether the maximum value is included in the range of allowed values.
minimum Defines the minimum value allowed for a numeric property.
exclusiveMinimum Specifies whether the minimum value is included in the range of allowed values.
maxLength Defines the maximum length of a string property.
minLength Defines the minimum length of a string property.
pattern Defines a regular expression pattern that a string property must match.
items Defines the expected type of items in an array.
additionalItems Specifies whether additional items are allowed in an array.
maxItems Defines the maximum number of items allowed in an array.
minItems Defines the minimum number of items allowed in an array.
uniqueItems Specifies whether all items in an array must be unique.
contains Specifies that an array must contain at least one element that matches a given schema.
maxProperties Defines the maximum number of properties allowed in an object.
minProperties Defines the minimum number of properties allowed in an object.
required Specifies the list of required properties for an object.
properties Defines the set of properties that an object can have, along with their expected types.
patternProperties Defines a set of properties that an object can have, where the property names match a regular expression pattern.
additionalProperties Specifies whether additional properties are allowed for an object.
dependencies Defines the dependencies between properties in an object.
propertyNames Defines a schema for the names of properties in an object.
if, then, else Define conditional schema keywords that allow different validation rules to be applied depending on whether a condition is true or false.
allOf, anyOf, oneOf, not Define logical operators that can be used to combine multiple schemas.
$ref Specifies a reference to another schema, allowing it to be reused within a schema.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://example.com/schema.json",
  "title": "Example",
  "description": "This is an example schema.",
  "type": "object",
  "properties": {
    "stringProp": {
      "type": "string",
      "default": "default value",
      "minLength": 1,
      "maxLength": 100,
      "pattern": "^[a-zA-Z0-9]*$",
      "format": "email"
    },
    "numberProp": {
      "type": "number",
      "minimum": 0,
      "maximum": 100,
      "exclusiveMinimum": true,
      "exclusiveMaximum": true,
      "multipleOf": 0.5
    },
    "integerProp": {
      "type": "integer",
      "minimum": -100,
      "maximum": 100,
      "exclusiveMinimum": true,
      "exclusiveMaximum": true,
      "multipleOf": 2
    },
    "booleanProp": {
      "type": "boolean"
    },
    "objectProp": {
      "type": "object",
      "properties": {
        "innerStringProp": {
          "type": "string",
          "minLength": 1
        },
        "innerNumberProp": {
          "type": "number"
        }
      },
      "required": [
        "innerStringProp"
      ],
      "additionalProperties": false,
      "minProperties": 1,
      "maxProperties": 2,
      "propertyNames": {
        "pattern": "^[a-zA-Z0-9]*$"
      },
      "dependencies": {
        "innerStringProp": [
          "innerNumberProp"
        ]
      }
    },
    "arrayProp": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "minItems": 1,
      "maxItems": 5,
      "uniqueItems": true,
      "contains": {
        "pattern": "^[a-zA-Z0-9]*$"
      }
    },
    "nullProp": {
      "type": "null"
    }
  },
  "required": [
    "stringProp",
    "numberProp",
    "integerProp",
    "booleanProp",
    "objectProp",
    "arrayProp",
    "nullProp"
  ],
  "definitions": {
    "stringArray": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "dependencies": {
    "stringProp": {
      "properties": {
        "stringDependentProp": {
          "$ref": "#/definitions/stringArray"
        }
      },
      "required": [
        "stringDependentProp"
      ]
    }
  },
  "if": {
    "properties": {
      "stringProp": {
        "const": "foo"
      }
    }
  },
  "then": {
    "required": [
      "numberProp"
    ]
  },
  "else": {
    "required": [
      "booleanProp"
    ]
  },
  "allOf": [
    {
      "properties": {
        "stringProp": {
          "minLength": 2
        }
      }
    },
    {
      "properties": {
        "stringProp": {
          "maxLength": 50
        }
      }
    }
  ],
  "anyOf": [
    {
      "properties": {
        "stringProp": {
          "enum": [
            "foo",
            "bar"
          ]
        }
      }
    },
    {
      "properties": {
        "numberProp": {
          "type": "number",
          "minimum": 50
        }
      }
    }
  ],
  "oneOf": [
    {
      "properties": {
        "stringProp": {
          "type": "string",
          "pattern": "^hello$"
        }
      }
    },
    {
      "properties": {
        "numberProp": {
          "type": "number",
          "maximum": 10
        }
      }
    },
    {
      "properties": {
        "booleanProp": {
          "type": "boolean",
          "enum": [
            true
          ]
        }
      }
    }
  ]
}
  • Then below is the valid json for the example json schema above.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "stringProp": "foo",
  "numberProp": 75.5,
  "integerProp": -98,
  "booleanProp": true,
  "objectProp": {
    "innerStringProp": "hello",
    "innerNumberProp": 42
  },
  "arrayProp": ["foo", "bar"],
  "nullProp": null
}
  • Below is the table that contains incompatible syntax between the draft-04 and draft-07 versions.
Syntax Description Compatibility
exclusiveMaximum and exclusiveMinimum In draft-04, these keywords were optional and defaulted to false. In draft-07, they are required and default to false and true, respectively. Incompatible
$ref In draft-04, $ref can reference any JSON Schema definition, including definitions defined inline. In draft-07, $ref can only reference definitions that are defined outside the current schema. Incompatible
additionalProperties and patternProperties In draft-04, these keywords were not mutually exclusive, meaning that both could be used to define properties in an object. In draft-07, they are mutually exclusive, and if patternProperties is used, additionalProperties can only be used to define boolean values. Incompatible
type In draft-04, type can be a string or an array of strings. In draft-07, type must be a string, and if multiple types are allowed, they must be listed in an array under the anyOf keyword. Incompatible
dependencies In draft-04, dependencies can only define the dependencies between properties in an object. In draft-07, dependencies can also define schema dependencies, where the presence or absence of a property is dependent on whether another property validates against a given schema. Incompatible
maximum and minimum In draft-04, maximum and minimum can be any numeric value. In draft-07, they must be finite numbers, and the use of NaN, Infinity, and -Infinity is prohibited. Incompatible
oneOf, anyOf, and allOf In draft-04, these keywords do not support if/then/else clauses, and their behavior is not well-defined when used with other schema keywords like additionalProperties. In draft-07, they have well-defined behavior and can be used with if/then/else clauses. Incompatible

Json Schema Validation With Spring Boot#

  • Implementations of Json Schema are very popular and supports many programming languages, you can view this page for more details.
  • In Java, there are also many Json Schema Implementations and in this example we will use the library everit-json-schema because this library is the most common with highest users and rating and it also supported 3 versions: draft-04, draft-06 and draft-07.

Prepare#

  • Firstly, let's take a look about the example that we are going to do. So let's images that we have a spring boot application service that is used for storing customer information.
  • The customer information is actually a json with contains some information like identity, orders and items and there some constraints such as the identity field is only mandatory for the ageRange which is ADULT, in case the ageRange is CHILDREN or there is no field ageRange it is the optional etc.... Let's see the example customer json as below.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
    "customer": {
        "fullName": "Duc Nguyen",
        "email": "example@gmail.com",
        "data": {
            "fullName": "Duc Nguyen",
            "email": "example@gmail.com",
            "ageRange": "ADULT",
            "identity": {
                "identityNumber": "ABCE-123-421-942",
                "address": "123 XYZ Street",
                "dob": "20/02/1995"
            },
            "orders": [
                {
                    "orderName": "Duc Order",
                    "createdDate": "2023-03-28T14:11:44.849+0700",
                    "lastUpdatedDate": "2023-03-28T14:11:44.849+0700",
                    "items": [
                        {
                            "itemName": "PS5",
                            "quantity": 1,
                            "price": 599.99
                        }
                    ]
                }
            ]
        }
    }
}
  • The Json Schema and Customer Data will be stored in a collection in the MongoDB in which when we are going to create a customer then the json schema will be loaded from the MongoDB to check the input customer json is valid or not. Let's see the diagram below.

 #zoom

  • Before deep dive into the example, we need to prepare the MongoDB. We can use the docker to create it quickly, you can view Docker With Databases for more details.

Dependencies#

  • Now, let's add some dependencies as below into our pom.xml for Spring MongoDB and everit-json-schema.
pom.xml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
        <version>2.6.3</version>
</dependency>

<dependency>
        <groupId>com.github.erosb</groupId>
        <artifactId>everit-json-schema</artifactId>
        <version>1.14.2</version>
</dependency>

Config#

  • Let's create a package config and add the configuration class ValidationConfig as below to set up validation for MongoDB operations.
ValidationConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.springboot.project.json.schema.validator.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class ValidationConfig {

    @Bean
    public ValidatingMongoEventListener validatingMongoEventListener() {
        return new ValidatingMongoEventListener(validator());
    }

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }

}
  • In this configuration class, we will create a bean validator by the new instance of LocalValidatorFactoryBean in which the LocalValidatorFactoryBean provides an implementation of javax.validation.Validator.
  • Then the bean validator will be used as a parameter to create a bean validatingMongoEventListener by a new instance of ValidatingMongoEventListener, this instance will listen MongoDB events and validates entities before they are saved to the database by using javax.validation.Validator in the input parameter.

  • Next, in the main class @EnableMongoRepositories we will use the annotation @EnableMongoRepositories to enable MongoDB repositories in the application. When used, Spring will look for interfaces that extend the MongoRepository interface and create instances of them automatically.

JsonSchemaValidatorApplication.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.springboot.project.json.schema.validator;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@SpringBootApplication
@EnableMongoRepositories
public class JsonSchemaValidatorApplication {

    public static void main(String[] args) {
        SpringApplication.run(JsonSchemaValidatorApplication.class, args);
    }

}

Model#

  • Next, let's create a package model and create model classes as below:

    • Customer: This model will be mapped with the collection customers in MongoDB.
    • DatabaseSequence: This model will be mapped with the collection database_sequence in MongoDB.
    • JsonSchemaValidator: This model will be mapped with the collection # json_schemas in MongoDB
    • JsonValidationResponse: This is just a simple DTO which is used for the response of Get Json Schema Validation api.
  • Firstly, let's create Customer model as below.

Customer.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.springboot.project.json.schema.validator.model;  

import lombok.Getter;  
import lombok.Setter;  
import org.springframework.data.mongodb.core.mapping.Document;  

import java.util.UUID;  

import javax.persistence.Id;  
import javax.validation.constraints.NotNull;  

@Getter  
@Setter  
@Document("customers")  
public class Customer {  

    @Id  
    private UUID id;  
    @NotNull  
    private String fullName;  
    @NotNull  
    private String email;  
    private org.bson.Document data;  

}
  • As you can see, in this class we will use the annotation @Document to map this class to the customers MongoDB collection.
  • In this class, the field data will contain all the customer details.

  • Next, we will continue to create the DatabaseSequence class. This class will be mapped with database_sequence collection, this collection is used for generating sequence number which is used for field version in JsonSchemaValidator class.

DatabaseSequence.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.springboot.project.json.schema.validator.model;

import javax.persistence.Id;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@org.springframework.data.mongodb.core.mapping.Document("database_sequence")
public class DatabaseSequence {

    @Id
    @NotNull
    private String id;
    @Min(value = 1, message = "version must be started from 1")
    private Long version;

}
  • Next, We will create the JsonSchemaValidator class. This class will be mapped with json_schemas collection, this collection is used for storing json schemas which will be used for validating the customer json before saving into the MongoDB.
JsonSchemaValidator.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.springboot.project.json.schema.validator.model;

import lombok.Getter;
import lombok.Setter;
import org.bson.Document;


import java.util.UUID;

import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Getter
@Setter
@org.springframework.data.mongodb.core.mapping.Document("json_schemas")
public class JsonSchemaValidator {

    @Id
    @NotNull
    private UUID id;
    @NotNull(message = "name of schema can't be null")
    private String name;
    private Long version;
    private String status;
    @NotNull(message = "value of schema can't be null")
    private Document value;

}
  • Finally, we just need to create a simple JsonValidationResponse as below.
JsonValidationResponse.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.springboot.project.json.schema.validator.model;

import org.bson.Document;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class JsonValidationResponse {

    private boolean validJson;
    private Long schemaVersion;
    private Document jsonInput;

}

Repository#

  • Now, we will continue to create the package repository and add 2 repositories classes CustomerRepository and JsonSchemaRepository as below.
CustomerRepository.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package com.springboot.project.json.schema.validator.repository;  

import com.springboot.project.json.schema.validator.model.Customer;  
import org.springframework.data.mongodb.repository.MongoRepository;  
import org.springframework.stereotype.Repository;  

@Repository  
public interface CustomerRepository extends MongoRepository<Customer, Long> {  

}
  • The CustomerRepository interface extends MongoRepository, which provides basic CRUD operations for the Customer class, such as save, findAll, findById, and deleteById.
JsonSchemaRepository
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.springboot.project.json.schema.validator.repository;  

import com.springboot.project.json.schema.validator.model.JsonSchemaValidator;  
import org.springframework.data.mongodb.repository.MongoRepository;  
import org.springframework.stereotype.Repository;  

import java.util.List;  

@Repository  
public interface JsonSchemaRepository extends MongoRepository<JsonSchemaValidator, Long> {  

    List<JsonSchemaValidator> findJsonSchemaValidatorByName(String name);  

    List<JsonSchemaValidator> findByOrderByVersionDesc(String name);  

}
  • Likewise JsonSchemaRepository interface extends MongoRepository, which provides basic CRUD operations for the JsonSchemaValidator class and in repository we also need to add some custom queries for finding Json SchemaValidator by name and by name with order by version.

Service#

  • So let's create 3 services and 1 component class as below:

    • SequenceGeneratorService: used for generate sequence version number.
    • CustomerService: used for creating and getting customer data.
    • JsonSchemaValidatorService: userd for validating json data, creating and getting json schemas.
    • CustomDateTimeValidator: This component is used for overriding the default DateTimeFormatValidator when building SchemaLoader in JsonSchemaValidatorService.
  • Firstly, let's create the SequenceGeneratorService class as below.

SequenceGeneratorService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.springboot.project.json.schema.validator.service;  

import java.util.Objects;  

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.data.mongodb.core.FindAndModifyOptions;  
import org.springframework.data.mongodb.core.MongoOperations;  
import org.springframework.data.mongodb.core.query.Criteria;  
import org.springframework.data.mongodb.core.query.Query;  
import org.springframework.data.mongodb.core.query.Update;  
import org.springframework.stereotype.Service;  

import com.springboot.project.json.schema.validator.model.DatabaseSequence;  

import lombok.RequiredArgsConstructor;  

@Service  
@RequiredArgsConstructor(onConstructor = @__(@Autowired))  
public class SequenceGeneratorService {  

    private final MongoOperations mongoOperations;  

    public long generateVersionSequence(String seqName) {  
        if (Objects.isNull(seqName))   
            throw new IllegalArgumentException("seqName must not be null!");  
        DatabaseSequence counter = mongoOperations.findAndModify(  
            Query.query(Criteria.where("_id").is(seqName)),  
                new Update().inc("version",1),   
FindAndModifyOptions.options().returnNew(true).upsert(true),  
                DatabaseSequence.class);  
        return !Objects.isNull(counter) ? counter.getVersion() : 1;  
    }  

}
  • In this service we will use the mongoOperations to query the DatabaseSequence record inside the database_sequence collection with id is the input seqName. If the record is existed then we will update the value by increasing current value with 1 else we will create a new record with value is 1.

  • Secondly, let's create the CustomDateTimeValidator component class as below.

CustomDateTimeValidator.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.springboot.project.json.schema.validator.service;  

import java.util.Arrays;  
import java.util.Collections;  
import java.util.List;  
import java.util.Optional;  

import org.everit.json.schema.FormatValidator;  
import org.joda.time.DateTimeFieldType;  
import org.joda.time.format.DateTimeFormatter;  
import org.joda.time.format.DateTimeFormatterBuilder;  
import org.joda.time.format.DateTimeParser;  
import org.springframework.stereotype.Component;  

@Component  
public class CustomDateTimeValidator implements FormatValidator {  

    private static final String DATE_TIME_FORMAT = "date-time";  
    private static final List<String> FORMATS = Collections.unmodifiableList(  
        Arrays.asList("yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss.[0-9]{1,12}Z")  
    );  
    private static final DateTimeFormatter FORMATTER;  

    static {  
        final DateTimeParser secFracsParser = new DateTimeFormatterBuilder()  
            .appendLiteral('.').appendFractionOfSecond(1,12)  
            .toParser();  

        final DateTimeParser timeZoneOffset = new DateTimeFormatterBuilder()  
            .appendTimeZoneOffset("Z", false, 2, 2)  
            .toParser();      
        DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();  

        builder = builder.appendFixedDecimal(DateTimeFieldType.year(), 4)  
            .appendLiteral('-')  
            .appendFixedDecimal(DateTimeFieldType.monthOfYear(), 2)  
            .appendLiteral('-')  
            .appendFixedDecimal(DateTimeFieldType.dayOfMonth(), 2)  
            .appendLiteral('T')  
            .appendFixedDecimal(DateTimeFieldType. hourOfDay(), 2)  
            .appendLiteral(':')  
            .appendFixedDecimal(DateTimeFieldType.minuteOfHour(), 2)  
            .appendLiteral(':')  
            .appendFixedDecimal(DateTimeFieldType.secondOfMinute(), 2)  
            .appendOptional(secFracsParser)  
            .appendOptional(timeZoneOffset);  

        FORMATTER = builder.toFormatter();  
    }  

    @Override  
    public Optional<String> validate(String dateTime) {  
        try {  
            FORMATTER.parseDateTime(dateTime);  
            return Optional.empty();  
        } catch(IllegalArgumentException ex) {  
            return Optional.of(String.format("[%s] is not a valid %s. Expected %s", dateTime, formatName(), FORMATS));  
        }  
    }  

    @Override  
    public String formatName() {  
        return DATE_TIME_FORMAT;  
    }  

}
  • Actually, we are going to create a custom DateTimeFormatValidator which will be used to replace the default DateTimeFormatValidator of this everit-json-schema. So we have to implement the interface FormatValidator to create a new custom DateTimeFormatValidator. In this component, we will override 2 methods formatName and validate.

    • In which, the formatName is used for the name of the FormatValidator that we want to set, if the name is already existed then later when we configure the SchemaLoader we will override it, if the name is new and has not existed as in the library then it will be the new custom format when we add it to the SchemaLoader and we can use it in the json schema.
    • In the method validate we will receive the string value which we are expecting as the datetime and then we will use the custom DateTimeFormatterBuilder to verify this string value. The datetime value should have the formats as in the example below:
      • 2023-03-28T14:11:44.849+0700
      • 2023-03-28T14:11:44.849Z
      • 2023-03-28T14:11:44
  • Next, let's create the JsonSchemaValidatorService as below.

JsonSchemaValidatorService.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package com.springboot.project.json.schema.validator.service;

import com.springboot.project.json.schema.validator.model.JsonSchemaValidator;
import com.springboot.project.json.schema.validator.model.JsonValidationResponse;
import com.springboot.project.json.schema.validator.repository.JsonSchemaRepository;

import lombok.RequiredArgsConstructor;

import org.bson.Document;
import org.everit.json.schema.Schema;
import org.everit.json.schema.ValidationException;
import org.everit.json.schema.loader.SchemaLoader;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class JsonSchemaValidatorService {

    private static final ConcurrentHashMap<String, String> JSON_SCHEMA_STRUCTURE_CACHE = new ConcurrentHashMap<>();

    private final JsonSchemaRepository jsonSchemaRepository;
    private final CustomDateTimeValidator customDateTimeValidator;
    private final SequenceGeneratorService sequenceGeneratorService;

    public JsonSchemaValidator createJsonSchemaValidator(String json) {
        if (!this.validateInputJsonSchemaStructure(json))
            throw new IllegalArgumentException("Validation failed for input json schema!");
        JsonSchemaValidator jsonSchemaValidator = new JsonSchemaValidator();
        Document data = Document.parse(json);
        String schemaName = data.getString("name");
        Document value = data.get("value", Document.class);
        Optional<JsonSchemaValidator> latestJsonSchemaOpt = this.getlatestActiveJsonSchemaVersion(schemaName);
        if (!latestJsonSchemaOpt.isPresent()) {
            return this.saveNewJsonSchemaValidator(jsonSchemaValidator, schemaName, value);
        }
        JsonSchemaValidator latestJsonSchema = latestJsonSchemaOpt.get();
        latestJsonSchema.setStatus("inactive");
        this.jsonSchemaRepository.save(latestJsonSchema);
        this.saveNewJsonSchemaValidator(jsonSchemaValidator, schemaName, value);
        return this.jsonSchemaRepository.save(jsonSchemaValidator);
    }

    public List<JsonSchemaValidator> getJsonSchemaValidatorByName(String name) {
        return this.jsonSchemaRepository.findJsonSchemaValidatorByName(name);
    }

    public JsonValidationResponse validateJsonData(String schemaName, String jsonData) {
        Optional<JsonSchemaValidator> latestJsonSchemaOpt = this.getlatestActiveJsonSchemaVersion(schemaName);
        if (!latestJsonSchemaOpt.isPresent()) {
            return this.createJsonValidationResponse(false, jsonData, 0L);
        }
        JsonSchemaValidator latestJsonSchema = latestJsonSchemaOpt.get();
        Document jsonSchema = latestJsonSchema.getValue();
        boolean isValidJson = this.validateJson(jsonSchema.toJson(), jsonData);
        return this.createJsonValidationResponse(isValidJson, jsonData, latestJsonSchema.getVersion());
    }

    private JsonValidationResponse createJsonValidationResponse(boolean isValidJson, String jsonData, Long version) {
        JsonValidationResponse jsonValidationResponse = new JsonValidationResponse();
        jsonValidationResponse.setValidJson(isValidJson);
        jsonValidationResponse.setJsonInput(Document.parse(jsonData));
        jsonValidationResponse.setSchemaVersion(version);
        return jsonValidationResponse;
    }

    private JsonSchemaValidator saveNewJsonSchemaValidator(JsonSchemaValidator jsonSchemaValidator, String schemaName, Document data) {
        jsonSchemaValidator.setId(UUID.randomUUID());
        jsonSchemaValidator.setName(schemaName);
        jsonSchemaValidator.setStatus("active");
        jsonSchemaValidator.setVersion(sequenceGeneratorService.generateVersionSequence(schemaName));
        jsonSchemaValidator.setValue(data);
        return this.jsonSchemaRepository.save(jsonSchemaValidator);
    }

    private Optional<JsonSchemaValidator> getlatestActiveJsonSchemaVersion(String schemaName) {
        List<JsonSchemaValidator> jsonSchemaValidators = this.jsonSchemaRepository.findByOrderByVersionDesc(schemaName);
        for (JsonSchemaValidator jsonSchemaValidator: jsonSchemaValidators) {
            if (jsonSchemaValidator.getStatus().equalsIgnoreCase("active")) {
                return Optional.of(jsonSchemaValidator);
            }
        }
        return Optional.empty();

    }

    private boolean validateInputJsonSchemaStructure(String target) {
        String jsonSchemaStructure = JSON_SCHEMA_STRUCTURE_CACHE.computeIfAbsent("jsonSchemaStructureValidator", (s) -> {
            ClassPathResource resource = new ClassPathResource("validation/JsonSchemaStructureValidator.json");
            try {
                return StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                throw new RuntimeException("There is no JsonSchemaStructureValidator in the source code!", e);
            }
        });
        return this.validateJson(jsonSchemaStructure, target);
    }

    private boolean validateJson(String validationSchema, String target) {
        try {
            SchemaLoader loader = SchemaLoader.builder()
                .addFormatValidator(customDateTimeValidator)
                .schemaJson(new JSONObject(validationSchema))
                .enableOverrideOfBuiltInFormatValidators()
                .build();
            Schema schema = loader.load().build();
            schema.validate(new JSONObject(target));
            return true;
        } catch (ValidationException e) {
            return false;
        }
    }

}
  • In this service class we will have 3 main methods, createJsonSchemaValidator, getJsonSchemaValidatorByName and validateJsonData.
  • In the method createJsonSchemaValidator firstly we will validate the input body by using another json schem which is put in the resources as below.
JsonSchemaStructureValidator.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
{
  "$id": "http://example.com/example.json",
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema",
  "required": [
    "name",
    "value"
  ],
  "properties": {
    "name": {
      "$id": "#/properties/name",
      "type": "string",
      "minLength": 1
    },
    "value": {
      "$id": "#/properties/value",
      "type": "object",
      "required": [
        "properties",
        "$id",
        "$schema",
        "type",
        "required"
      ],
      "properties": {
        "$id": {
          "$id": "#/properties/$id",
          "type": "string",
          "minLength": 1
        },
        "type": {
          "$id": "#/properties/type",
          "type": "string",
          "minLength": 1
        },
        "$schema": {
          "$id": "#/properties/$schema",
          "type": "string",
          "minLength": 1
        },
        "required": {
          "$id": "#/properties/required",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "properties": {
          "$id": "#/properties/properties",
          "type": "object",
          "additionalProperties": true
        }
      }
    },
    "additionalProperties": true
  }
}
  • This json schema will validate the input json scheme in the request body to make sure the request body and the structure of in json schema validation is correct.

  • Then we will get the latest json schema version base on the schema name. If the schema existed in the MongoDB, then we will set the status inactive for it and we will create a new record with newer active version. If the schema isn't existed before then we will create a new one.

  • In the method getJsonSchemaValidatorByName, we just simply query all the json schemas by schema name.
  • Finally in the method validateJsonData, we will get the latest json schema version, if the json schema doesn't exist, then we will return validation failed. If it exists, then we will create an instance of SchemaLoader by using SchemaLoader.builder()

    • Then we will set the customDateTimeValidator to the SchemaLoader that we created before.
    • Then The schemaJson(new JSONObject(validationSchema)) method loads the JSON schema from the MongoDB that we get in the step before.
    • The we use the enableOverrideOfBuiltInFormatValidators() method enables the override of the built-in format validators that come with the library.
    • Finally, the build() method builds the SchemaLoader instance.
  • Next we will use load().build(); to load the schema and builds it.

  • Finally we use validate(new JSONObject(target)); to validate the JSON object target against the JSON schema. If the validation fails, a ValidationException is thrown.

  • Next, we will continue to create the CustomerService class as below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.springboot.project.json.schema.validator.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.project.json.schema.validator.model.Customer;
import com.springboot.project.json.schema.validator.model.JsonValidationResponse;
import com.springboot.project.json.schema.validator.repository.CustomerRepository;

import lombok.RequiredArgsConstructor;

import java.util.List;
import java.util.UUID;

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CustomerService {

    private final CustomerRepository customerRepository;
    private final JsonSchemaValidatorService jsonSchemaValidatorService;
    private final ObjectMapper objectMapper;

    public Customer createCustomer(String customerJson) {
        JsonValidationResponse jsonValidationResponse = this.jsonSchemaValidatorService.validateJsonData("CustomerJsonSchemaValidator", customerJson);
        if (jsonValidationResponse.isValidJson()) {
            Customer customer = new Customer();
            JsonNode customerNode;
            try {
                customerNode = this.objectMapper.readTree(customerJson);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                throw new IllegalArgumentException("Customer Json is not Valid: " + customerJson);
            }
            Document customerData = Document.parse(customerJson);
            customer.setId(UUID.randomUUID());
            customer.setFullName(customerNode.get("customer").get("fullName").asText());
            customer.setEmail(customerNode.get("customer").get("email").asText());
            customer.setData(customerData);
            return this.customerRepository.save(customer);
        }
        throw new IllegalArgumentException("Customer Json is not Valid: " + customerJson);
    }

    public List<Customer> getCustomers() {
       return this.customerRepository.findAll();
    }

}
  • In this service class we will have 2 methods, createCustomer and getCustomers. In which:
    • In the method createCustomer, we will use the JsonSchemaValidatorService to validate the input json, if the json is valid then we will create the customer else we will thrown an exception.
    • Next, in the method getCustomers, we just simply get all the created customers in the MongoDB.

Controller#

  • Next we create 2 controllers CustomerController and JsonSchemaValidatorController respectively as below.
CustomerController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.springboot.project.json.schema.validator.controller;

import com.springboot.project.json.schema.validator.model.Customer;
import com.springboot.project.json.schema.validator.service.CustomerService;

import lombok.RequiredArgsConstructor;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CustomerController {

    private final CustomerService customerService;

    @RequestMapping(method = RequestMethod.POST, path = "/v1/customers")
    public ResponseEntity<Customer> createCustomer(@RequestBody String customerJson) {
        return new ResponseEntity<>(this.customerService.createCustomer(customerJson), HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/customers")
    public ResponseEntity<List<Customer>> getCustomers() {
        return new ResponseEntity<>(this.customerService.getCustomers(), HttpStatus.OK);
    }

}
  • In this controller, we simply create 2 apis for creating customer and getting all the customers in the MongoDB.
JsonSchemaValidatorController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.springboot.project.json.schema.validator.controller;

import com.springboot.project.json.schema.validator.model.JsonSchemaValidator;
import com.springboot.project.json.schema.validator.model.JsonValidationResponse;
import com.springboot.project.json.schema.validator.service.JsonSchemaValidatorService;

import lombok.RequiredArgsConstructor;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class JsonSchemaValidatorController {

    private final JsonSchemaValidatorService jsonSchemaValidatorService;

    @RequestMapping(method = RequestMethod.POST, path = "/v1/json/validator/schemas")
    public ResponseEntity<JsonSchemaValidator> createJsonSchemaValidator(@RequestBody String jsonSchema) {
        return new ResponseEntity<>(this.jsonSchemaValidatorService.createJsonSchemaValidator(jsonSchema), HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/json/validator/schemas/{schemaName}")
    public ResponseEntity<List<JsonSchemaValidator>> getJsonSchemaValidatorByName(@PathVariable("schemaName") String schemaName) {
        return new ResponseEntity<>(this.jsonSchemaValidatorService.getJsonSchemaValidatorByName(schemaName), HttpStatus.OK);
    }

    @RequestMapping(method = RequestMethod.POST, path = "/v1/json/validator/schemas/{schemaName}")
    public ResponseEntity<JsonValidationResponse> validateCustomerJson(@PathVariable("schemaName") String schemaName, @RequestBody String customerJson) {
        return new ResponseEntity<>(this.jsonSchemaValidatorService.validateJsonData(schemaName, customerJson), HttpStatus.OK);
    }

}
  • In this controller, we will create 3 apis for creating json schema, getting json schema by name and validate json with json schema. They are 3 apis for 3 main methods in the JsonSchemaValidatorService.

Testing#

  • Okay let's take an example json schema as below:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
{
    "name": "CustomerJsonSchemaValidator",
    "value": {
        "$id": "http://example.com/example.json",
        "type": "object",
        "$schema": "http://json-schema.org/draft-07/schema",
        "required": [
            "customer"
        ],
        "properties": {
            "customer": {
                "$id": "#/properties/customer",
                "type": "object",
                "description": "customer object",
                "additionalProperties": true,
                "required": [
                    "fullName",
                    "email",
                    "data"
                ],
                "properties": {
                    "fullName": {
                        "$id": "#/properties/customer/properties/fullName",
                        "type": "string",
                        "description": "fullName of user"
                    },
                    "email": {
                        "$id": "#/properties/customer/properties/email",
                        "type": "string",
                        "description": "email of user"
                    },
                    "data": {
                        "$id": "#/properties/customer/properties/data",
                        "type": "object",
                        "description": "all user data",
                        "additionalProperties": true,
                        "properties": {
                            "fullName": {
                                "$id": "#/properties/customer/properties/data/properties/fullName",
                                "type": "string",
                                "description": "fullName of user"
                            },
                            "email": {
                                "$id": "#/properties/customer/properties/data/properties/email",
                                "type": "string",
                                "description": "email of user"
                            },
                            "orders": {
                                "$id": "#/properties/customer/properties/data/properties/order",
                                "type": [
                                    "array"
                                ],
                                "minItems": 0,
                                "description": "orders array",
                                "additionalProperties": true,
                                "items": {
                                    "type": "object",
                                    "required": [
                                        "orderName",
                                        "createdDate",
                                        "lastUpdatedDate",
                                        "items"
                                    ],
                                    "properties": {
                                        "orderName": {
                                            "$id": "#/properties/customer/properties/data/properties/order/properties/orderName",
                                            "type": "string",
                                            "description": "orderName"
                                        },
                                        "createdDate": {
                                            "$id": "#/properties/customer/properties/data/properties/order/properties/createdDate",
                                            "type": "string",
                                            "format": "date-time",
                                            "description": "createdDate with formats: 2020-05-23T18:25:43.511Z, 2021-05-17T13:56:32, 2023-03-28T14:11:44.849+0700"
                                        },
                                        "lastUpdatedDate": {
                                            "$id": "#/properties/customer/properties/data/properties/order/properties/lastUpdatedDate",
                                            "type": "string",
                                            "format": "date-time",
                                            "description": "lastUpdatedDate with formats: 2020-05-23T18:25:43.511Z, 2021-05-17T13:56:32, 2023-03-28T14:11:44.849+0700"
                                        },
                                        "items": {
                                            "$id": "#/properties/customer/properties/data/properties/order/properties/items",
                                            "type": "array",
                                            "minItems": 1,
                                            "description": "list of items",
                                            "items": {
                                                "type": "object",
                                                "required": [
                                                    "itemName",
                                                    "quantity",
                                                    "price"
                                                ],
                                                "properties": {
                                                    "itemName": {
                                                        "$id": "#/properties/customer/properties/data/properties/order/properties/items/properties/itemName",
                                                        "type": "string",
                                                        "description": "itemName"
                                                    },
                                                    "quantity": {
                                                        "$id": "#/properties/customer/properties/data/properties/order/properties/items/properties/quantity",
                                                        "type": "integer",
                                                        "format": "int64",
                                                        "description": "quantity - Ex: 10 or 20 or 45..."
                                                    },
                                                    "price": {
                                                        "$id": "#/properties/customer/properties/data/properties/order/properties/items/properties/price",
                                                        "type": "number",
                                                        "description": "price - Ex: 30.5 or 105.9"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        },
                        "if": {
                            "properties": {
                                "ageRange": {
                                    "enum": [
                                        "ADULT"
                                    ]
                                }
                            },
                            "required": [
                                "ageRange"
                            ]
                        },
                        "then": {
                            "required": [
                                "fullName",
                                "email",
                                "identity"
                            ],
                            "properties": {
                                "additionalProperties": true,
                                "identity": {
                                    "$id": "#/properties/customer/properties/identity",
                                    "type": "object",
                                    "description": "identity information",
                                    "required": [
                                        "identityNumber",
                                        "address",
                                        "dob"
                                    ],
                                    "properties": {
                                        "identityNumber": {
                                            "$id": "#/properties/customer/properties/identity/properties/identityNumber",
                                            "type": "string",
                                            "description": "identityNumber"
                                        },
                                        "address": {
                                            "$id": "#/properties/customer/properties/identity/properties/address",
                                            "type": "string",
                                            "description": "address"
                                        },
                                        "phone": {
                                            "$id": "#/properties/customer/properties/identity/properties/phone",
                                            "type": "string",
                                            "description": "phone"
                                        },
                                        "dob": {
                                            "$id": "#/properties/customer/properties/identity/properties/dob",
                                            "type": "string",
                                            "description": "dob"
                                        },
                                        "sex": {
                                            "$id": "#/properties/customer/properties/identity/properties/sex",
                                            "type": "string",
                                            "description": "sex"
                                        }
                                    }
                                }
                            }
                        },
                        "else": {
                            "required": [
                                "fullName",
                                "email"
                            ],
                            "properties": {
                                "additionalProperties": true,
                                "identity": {
                                    "$id": "#/properties/customer/properties/identity",
                                    "type": "object",
                                    "description": "identity information",
                                    "required": [
                                        "identityNumber",
                                        "address",
                                        "dob"
                                    ],
                                    "properties": {
                                        "identityNumber": {
                                            "$id": "#/properties/customer/properties/identity/properties/identityNumber",
                                            "type": "string",
                                            "description": "identityNumber"
                                        },
                                        "address": {
                                            "$id": "#/properties/customer/properties/identity/properties/address",
                                            "type": "string",
                                            "description": "address"
                                        },
                                        "phone": {
                                            "$id": "#/properties/customer/properties/identity/properties/phone",
                                            "type": "string",
                                            "description": "phone"
                                        },
                                        "dob": {
                                            "$id": "#/properties/customer/properties/identity/properties/dob",
                                            "type": "string",
                                            "description": "dob"
                                        },
                                        "sex": {
                                            "$id": "#/properties/customer/properties/identity/properties/sex",
                                            "type": "string",
                                            "description": "sex"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "description": "The root schema comprises the entire JSON document.",
        "additionalProperties": true
    }
}
  • In this json schema we will validate the customer json to check it is valid or not. The valid customer json is showed as below.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
    "customer": {
        "fullName": "Duc Nguyen",
        "email": "example@gmail.com",
        "data": {
            "fullName": "Duc Nguyen",
            "email": "example@gmail.com",
            "ageRange": "ADULT",
            "identity": {
                "identityNumber": "ABCE-123-421-942",
                "address": "123 XYZ Street",
                "dob": "20/02/1995"
            },
            "orders": [
                {
                    "orderName": "Duc Order",
                    "createdDate": "2023-03-28T14:11:44.849+0700",
                    "lastUpdatedDate": "2023-03-28T14:11:44.849+0700",
                    "items": [
                        {
                            "itemName": "PS5",
                            "quantity": 1,
                            "price": 599.99
                        }
                    ]
                }
            ]
        }
    }
}
  • In the customer json, it contains some information like identity, orders and items and there some constraints such as the identity field is only mandatory for the ageRange which is ADULT, in case the ageRange is CHILDREN or there is no field ageRange it is the optional. Then for the list orders, it can be empty because the customer haven't had any order yet. Then if the customer has an order, then all the information in the order must be required, included the list items.

  • Now, Firstly we will use the postman to add the json schema validator as below. Then we will receive the newest version with active status.

 #zoom

  • Then we can use the api get json schema by name to get all the json schema version to make sure the json schema is saved successfully into the MongoDB.

 #zoom

  • Finally, now, we can try to validate the customer json by using api validateCustomerJson. So if we put the valid json we will see the response with field validJson as true.

 #zoom

  • We try to set the field identity as null then you will see the validJson as false because the ageRange is ADULT and it requires the field identity.

 #zoom

  • But if we change the ageRange to CHILDREN, then you will see the customer json is valid again.

 #zoom

  • Next, we will try to create the customer with valid json, then you will see the successful result as below.

 #zoom

  • If we make the customer json invalid then we will got the internal error.

 #zoom

  • Then to make sure the customer data is created successfully we can use the api get customers to check.

 #zoom

See Also#

References#