Json Schema Generator with inheritance and references - java

I am trying to generate a JSON schema using POJOs with deep inheritance structure.
Using jackson-module-jsonSchema library I am able to generate a schema.
Given a simplified Java example:
public interface I {...}
public class A implements I {
public int varA;
}
public class B implements I {
public int varB;
}
public class C {
public I varC;
}
Below is my code to generate the schema:
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.module.jsonSchema.*
// ...
ObjectMapper mapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(mapper.constructType(C.class), visitor);
JsonSchema schema = visitor.finalSchema();
String outputSchemaJson = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(schema);
Actual Json Schema:
{
"type" : "object",
"id" : "urn:jsonschema:com:mycompany:GenerateSchemas:C",
"properties" : {
"varC" : {
"type" : "any"
}
}
}
Desired Json Schema:
{
"definitions": {
"A": {
"type" : "object",
"id" : "urn:jsonschema:com:mycompany:GenerateSchemas:A",
"properties" : {
"varA" : {
"type" : "integer"
}
}
},
"B": {
"type" : "object",
"id" : "urn:jsonschema:com:mycompany:GenerateSchemas:B",
"properties" : {
"varB" : {
"type" : "integer"
}
}
}
},
"type" : "object",
"id" : "urn:jsonschema:com:mycompany:GenerateSchemas:C",
"properties" : {
"varC" : {
"type" : "object",
"oneOf": [
{ "$ref": "urn:jsonschema:com:mycompany:GenerateSchemas:A" },
{ "$ref": "urn:jsonschema:com:mycompany:GenerateSchemas:B" }
]
}
}
}
I have tried overriding core classes from Json Schema library. This answer from stack overflow was helpful to generate a schema with references.
Now I am trying to understand what I need to override such that I can use reflection to get all inheriting-classes of an interface and add oneOf references to it.

I was finally able to figure out which classes I needed to override.
Notes:
Java does not support dynamically finding sub-classes via reflection through a simple out-of-box api. A workaround was to annotate classes with #JsonSubType which I was able to extract at run-time.
In version 2.9.8 of the json-module-schema library (where revision 1 of solution is written), there is no support yet for object definitions. Just to get the work done, I had to override a few extra classes to make this possible.
definitions need to be defined in the json schema only once at root level because there can be cases of recursive references.
With updated POJO code:
#JsonSubTypes({
#JsonSubTypes.Type(name = "A", value = A.class),
#JsonSubTypes.Type(name = "B", value = B.class)
})
public interface I {}
public class A implements I {
public int varA;
}
public class B implements I {
public int varB;
}
public class C {
public I varC;
}
The desired json schema output is produced successfully.
Given the following code: https://gist.github.com/rrmistry/2246c959d1c9cc45894ecf55305c61fd, I imported GenerateSchema class to make schema generation code more simplified:
public void generate() throws Exception {
generateSchemasFromJavaSubTypes(C.class);
}
private void generateSchemasFromJavaSubTypes(Class<?> classToGenerate) throws Exception {
JsonSchema schema = GenerateSchemas.generateSchemaFromJavaClass(classToGenerate);
ObjectMapper mapper = new ObjectMapper();
String jsonSchemaStr = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(schema);
}
GitHub issue has been created to request native support: https://github.com/FasterXML/jackson-module-jsonSchema/issues/135

Related

How to avoid object wrapper inside object in jackson

I was new to the spring restful with Jackson and Hibernate OGM. I'm getting JSON format as below from web request
"Order":
{
"Orderdata": [ {"orderNo" : "1","location":"A"},{"orderNo" : "2","location":"B"},..]
}
Pojo class for order
class Order implements Serializable{
#ElementCollection
private List<OrderData> Orderdata = null;
//getter and setter
}
After inserting the document into MongoDB. I'm getting as below
"Order": {
"Orderdata": [
{
"Orderdata": {"orderNo" : "1","location":"A"}
},
{
"Orderdata": {"orderNo" : "2","location":"B"}
},
...
]}
Here I need to avoid the "Orderdata" name inside the each document created. Please help me to resolve this issue.

How do I bind or resolve a Map that is spread amongst multiple PropertySource in a custom EnvironmentRepository for Spring Cloud Config server?

I wrote a custom Spring Cloud Config EnvironmentRepository for Spring Cloud Config Server. I had to given the internal requirements of this product. It loads a variety of files from the file system that are not in YAML or property file format, among other things.
One of my goals was to make each file a PropertySource that would yield unique keys to a map. However, it seems that only the first file based Property Source gets mapped in my configuration even though my config server exposes more.
Example, let's say a GET to cloud config:
http://localhost:8888/myproduct/default/master
yields the following response:
{
"name" : "myproduct",
"profiles" : [ "default" ],
"label" : "master",
"version" : null,
"state" : null,
"propertySources" : [ {
"name" : "database:application-default",
"source" : {
"spring.cloud.config.override-system-properties" : false
}
}, {
"name" : "database: myproduct-default",
"source" : {
"datasource.hostname" : "from#db.default",
"product.name" : "myproduct"
}
}, {
"name" : "cartridge-configs-1503",
"source" : {
"cartridge-configs[1503].cartridge.cartridgeId" : 1503,
"cartridge-configs[1503].cartridge.cartridgeName" : "One",
}
}, {
"name" : "cartridge-configs-1603",
"source" : {
"cartridge-configs[1603].cartridge.cartridgeId" : 1603,
"cartridge-configs[1603].cartridge.cartridgeName" : "Two"
}
}
} ]
}
And on the client, I have the following configuration property
#Component
#RefreshScope
#ConfigurationProperties
public class MyProductConfiguration {
private Map<String, CartridgeConfig> cartridgeConfigs = new LinkedHashMap<>();
public Map<String, CartridgeConfig> getCartridgeConfigs() {
return cartridgeConfigs;
}
public static class CartridgeConfig {
private long cartridgeId;
private String cartridgeName;
public setCartridgeId(long cartridgeId) {
this.cartridgeId = cartridgeId;
}
public long getCartridgeId() {
return cartridgeId;
}
public void setCartridgeName(String cartridgeName) {
this.cartridgeName = cartridgeName;
}
public String getCartridgeName() {
return cartridgeName;
}
}
}
It works, but only "cartridge-configs[1503]" loads. Why?
What is the proper resolution for this, or workaround?
I am guessing that the only work around is to consolidate all the different property sources that share the same key for the map "cartridge-configs" into one property source as opposed to multiple ones.

How to make POJO dynamic so that it ignores an json tag but reads the value under that tag using jackson in java?

I've a parent DAO:
#XmlRootElement//(name="metadata")
public class FolderAttributes {
private Map nameValueForListValue;
Child DAO:
#XmlAccessorType(XmlAccessType.FIELD)
public class ListWrapper {
#XmlElementWrapper(name = "attrValue")
private List<Object> list;
JSON request that works (if I use "metadata" name as root element):
"metadata": {
"nameValueForListValue": {
"signed": {
"attrValue": [
"ahsdfhgakjf"
]
},
"id": {
"attrValue": [
"12345678",
"87654321"
]
},
.......... continues
I don't want the tag "nameValueForListValue" in request, instead it should be smart enough to read rest of the values without that tag. Looks like it always needs to have the param name "nameValueForListValue" on the request. Is there any annotations that will do my job easier? I'm using Java 6 & jackson 1.9.
What about using #JsonAnySetter Jackson annotation
It would be something like:
#XmlRootElement//(name="metadata")
public class FolderAttributes {
private Map nameValueForListValue;
#JsonAnySetter
public void genericSetter(String key, Object value){
nameValueForListValue.put(key, value);
}
}
That whay any unknown field could be handle by this setter.
More info:#JsonAnySetter example
#JsonInclude(JsonInclude.Include.NON_NULL)

Jackson deserialization: Custom Object factory

How do you implement in Jackson a conversion from json to Java objects, based on class types specified in the json.
Example Java Types:
public class Car{
public String make;
public String model;
}
public class Spoon {
public String material;
}
public class Ownership {
public List<Object> items;
public User owner;
}
Example Json:
{
"items": [
{
"#class": "com.example.Car",
"make": "Mercedes-Benz",
"model": "S500"
},
{
"#class": "com.example.Spoon",
"material": "silver"
}
],
"owner": {
"name": "John"
}
}
Since the number of classes is unknown (users can add any class) it is not possible to use the annotation #JsonSubTypes.
In addition, the json may contain known strongly types classes, like the object User in the example which is serialized using the standard Jackson implementation.
Most of the examples I can find, such as http://www.baeldung.com/jackson-inheritance assume the number of subclasses is known, but in my case it is not, users of the framework will add their own.
Ideally the implementation will just resolve types and let Jackson do the rest of the serialization without repeating that code.
Can be solved using an annotation on the collection:
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
public List<Object> items;

Generating JsonSchema from pojo: how do I add "description" automatically?

I am trying to automatically generate JsonSchema from pojos in my project: The code looks like this:
ObjectMapper mapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(clazz, visitor);
JsonSchema jsonSchema = visitor.finalSchema();
String schemaString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
When clazz is defined like this:
public class ZKBean
{
public String anExample;
public int anInt;
}
I end up with this:
{
"type" : "object",
"id" : "urn:jsonschema:com:emc:dpad:util:ZKBean",
"properties" : {
"anInt" : {
"type" : "integer"
},
"anExample" : {
"type" : "string"
}
}
}
All that is great. What I want to do is add the "description" key to the schema, so that I instead have something that looks like:
{
"type" : "object",
"id" : "urn:jsonschema:com:emc:dpad:util:ZKBean",
"properties" : {
"anInt" : {
"type" : "integer",
"description" : "Represents the number of foos in the system"
},
"anExample" : {
"type" : "string",
"description" : "Some descriptive description goes here"
}
}
}
I assumed there was some annotation I could just put on the fields in my ZKBean class, but after half a day of futzing I have not found one. Is this the way to go? Or do I need to do something with my Visitor?
Thanks,
Jesse
You can use the #JsonPropertyDescription annotation for generating json schema which works since Jackson 2.4.1. Here is an example:
public class JacksonSchema {
public static class ZKBean {
#JsonPropertyDescription("This is a property description")
public String anExample;
public int anInt;
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(ZKBean.class, visitor);
JsonSchema jsonSchema = visitor.finalSchema();
System.out.println(mapper
.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema));
}
}
Output:
{
"type" : "object",
"id" : "urn:jsonschema:stackoverflow:JacksonSchema:ZKBean",
"properties" : {
"anExample" : {
"type" : "string",
"description" : "This is a property description"
},
"anInt" : {
"type" : "integer"
}
}
}
It appears that the problem lies not in the jackson libraries, but in the package I am using to generate the class objects. My code is pasted below; I suspect the Reflections object is dropping the annotation information (when I manually specify ZKBean.class the description appears). Thanks for the help!
Reflections reflections = new Reflections(
new ConfigurationBuilder().setUrls(
ClasspathHelper.forClassLoader(urlcl)).
addClassLoader(urlcl));
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(JsonBean.class);
ObjectMapper mapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
for (Class<?> clazz : annotated)
{
try
{
mapper.acceptJsonFormatVisitor(mapper.constructType(SampleBean.class), visitor);
JsonSchema jsonSchema = visitor.finalSchema();
String schemaString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
System.out.println(schemaString);
I had the same problem and identified another possible cause (just adding in hope this might help someone).
I am also loading Class objects (of DTO POJOs with Jackson annotations) using a custom ClassLoader and have them processed by ObjectMapper and SchemaFactoryWrapper to generate corresponding JSON schemas. In my case I forgot to add the jackson-annotations jar to the ClassLoader which leads to the described problem.

Categories