Generate JSON schema from Java class
This tutorial shows you how to generate JSON schema from Java class.
We will use an open source library called JJSchema to do the job. To be able to generate the JSON schema properly, the Java class should have getters and setters defined for its members.
A JSON Schema is a JSON document that describes the structure of the JSON data. JSON Schemas are to JSON as XML Schemas are to XML. JSON Schema specification is currently under draft and the latest version is v4.
JJSchema is an open source library hosted on Github that can generate the latest draft v4 compliant JSON schemas. It uses the popular Jackson JSON processor Java library, internally. Jackson, itself, lacks the capability to generate JSON Schema from a Java class and hence libraries like JJSchema come as a great help when it comes generating JSON schemas.
The current release of JJSchema is 0.6 while version 1.0 is under development. This tutorial uses its 0.6 version.
Below is the maven dependency for JJSchema.
Maven dependency
1 2 3 4 5 6 |
<dependency> <groupId>com.github.reinert</groupId> <artifactId>jjschema</artifactId> <version>0.6</version> </dependency> |
Like XML schemas, JSON schemas includes a list of meta data about the schema fields. A partial list of meta data is given below.
- Name of the field
- Datatype of the field
- An optional description of the field
- Indicator on whether the field is mandatory or optional
- Optional constraints on number of occurrences and length of the value of the field
JJSchema provides an annotation @Attributes to specify the above meta data for the members of the Java class. In the example below, we have the Java class called Employee with fields for id, first name, last name, age, gender and multiple address lines.
Using the @Attributes annotation, we enforce the following constraints on the members of the Java class:
- first name and last name fields cannot be empty. Can be maximum 15 character long.
- address cannot be empty and can have up to and not exceeding 3 address lines. And each line cannot exceed 30 characters in length.
- gender field can only have MALE or FEMALE as its value
Take a close look at the annotations on each class member of the Employee class shown below and relate them with the discussion here.
Note: It is not absolutely necessary to annotate the Java class to be able to generate a JSON schema from it. You can pretty well do that without the annotations. But, the difference would be that without the annotations all fields would be generated as optional fields and you will not be able to specify and enforce constraints, if any.
Employee class
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 |
package com.wilddiary.json; import java.util.List; import com.github.reinert.jjschema.Attributes; @Attributes(title = "Employee", description = "Schema for an employee") public class Employee { @Attributes(required = true, description = "ID of the employee") private int id; @Attributes(required = true, minLength = 1, maxLength = 15, description = "First name of the employee") private String firstName; @Attributes(required = true, minLength = 1, maxLength = 15, description = "Last name of the employee") private String lastName; @Attributes(required = true, description = "Age in years of the employee") private int age; @Attributes(required = true, description = "Gender of the employee") private Gender gender; @Attributes(required = true, minItems = 1, maxItems = 3, minLength = 1, maxLength = 30, description = "Address lines of the employee") private List address; // setters and getters are omitted for convinience } |
Now, after the Java class has been annotated, we are now ready to generate the JSON schema. Below is the main class that generates the schema.
Main class
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 |
package com.wilddiary.json; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; import com.github.reinert.jjschema.JsonSchemaGenerator; import com.github.reinert.jjschema.SchemaGeneratorBuilder; public class SchemaGeneratorTest { private static ObjectMapper mapper = new ObjectMapper(); public static final String JSON_$SCHEMA_DRAFT4_VALUE = "http://json-schema.org/draft-04/schema#"; public static final String JSON_$SCHEMA_ELEMENT = "$schema"; static { // required for pretty printing mapper.enable(SerializationFeature.INDENT_OUTPUT); } public static void main(String[] args) throws JsonProcessingException { // get the draft 4 schema builder JsonSchemaGenerator v4generator = SchemaGeneratorBuilder.draftV4Schema().build(); // use the schema builder to generate JSON schema from Java class JsonNode schemaNode = v4generator.generateSchema(Employee.class); // add the $schema node to the schema. By default, JJSchema v0.6 does not add it ((ObjectNode) schemaNode).put(JSON_$SCHEMA_ELEMENT, JSON_$SCHEMA_DRAFT4_VALUE); // print the generated schema prettyPrintSchema(schemaNode); } private static void prettyPrintSchema(JsonNode schema) throws JsonProcessingException{ System.out.println(mapper.writeValueAsString(schema)); } } |
Output
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 |
{ "type" : "object", "$schema" : "http://json-schema.org/draft-04/schema#", "description" : "Schema for an employee", "title" : "Employee", "required" : [ "address", "id", "gender", "firstName", "lastName", "age" ], "properties" : { "address" : { "type" : "array", "items" : { "type" : "string" }, "description" : "Address lines of the employee", "minItems" : 1, "maxItems" : 3, "minLength" : 1, "maxLength" : 30 }, "id" : { "type" : "integer", "description" : "ID of the employee" }, "gender" : { "enum" : [ "MALE", "FEMALE" ], "description" : "Gender of the employee" }, "firstName" : { "type" : "string", "description" : "First name of the employee", "minLength" : 1, "maxLength" : 15 }, "lastName" : { "type" : "string", "description" : "Last name of the employee", "minLength" : 1, "maxLength" : 15 }, "age" : { "type" : "integer", "description" : "Age in years of the employee" } } } |
Below is a sample valid JSON data that conforms to the JSON schema generates above.
Validate JSON data
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "id" : 101, "firstName" : "Guru", "lastName" : "Drona", "gender" : "MALE", "age" : 40, "address" : [ "Middlefield Road", "Menlo Park", "CA" ] } |
Below is a sample invalid JSON data that does not conform to the JSON schema generates above.
Invalid JSON data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "id" : 101, "firstName" : "Guru", "lastName" : "Drona", "gender" : "Masculine", "age" : 40, "address" : [ "Middlefield Road", "Menlo Park", "CA", "US" ] } |
I am trying to use this code and I get a stack overflow error. Apparently there is a limit to how big the class can be. Is there a way to specify a maximum depth to reflect?
this is not valid code….
1. gender property is not completely initialized.
2. after commenting above property still getting error as follows :
Exception in thread “main” java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
try this……………………
import java.io.Serializable;
import java.util.List;
import com.github.reinert.jjschema.Attributes;
@Attributes(title = “Employee”, description = “Schema for an employee”)
public class Employee implements Serializable {
@Attributes(required = true, description = “ID of the employee”)
private Integer id;
@Attributes(required = true, minLength = 1, maxLength = 15, description = “First name of the employee”)
private String firstName;
@Attributes(required = true, minLength = 1, maxLength = 15, description = “Last name of the employee”)
private String lastName;
@Attributes(required = true, description = “Age in years of the employee”)
private Integer age;
// @Attributes(required = true, description = “Gender of the employee”)
// private Gender gender;
@Attributes(required = true, minItems = 1, maxItems = 3, minLength = 1, maxLength = 30, description = “Address lines of the employee”)
private List<String> address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getAddress() {
return address;
}
public void setAddress(List<String> address) {
this.address = address;
}
}
this is not valid code….
1. gender property is not comletely initilised.
2. after commenting above property still geting error as follows :
Exception in thread “main” java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType