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

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.

Related

Define required fields in POJO class for JSON mapping

I am mapping my API JSON response to Java object using ObjectMapper (Jackson). Below is how my json looks like :
[
{
firstKey : "value1",
secondKey : "value2",
thirdKey : "value3"
},
{
firstKey : "value4",
secondKey : "value5",
thirdKey : "value6"
}
]
Required fields are :
firstKey
secondKey
thirdKey
Some of my JSON responses might not have all these three required fields, for which I would like Jackson to throw exception while deserializing. How should I let Jackson know about the required fields ? Is there any annotation for it, except JsonProperty(required = true) since this does not works ?
Also, if a key has null value, it is accepted default value, so I cannot use #NotNull as well. For eg :
[
{
firstKey : null,
secondKey : "value2",
thirKey : "value3"
}
]
Above is valid JSON and should parsed without any exception during deserialization.
Validation functionality overall is not implemented in Jackson since it is considered to be out of scope, see for example Jackson - Required property?.
And some information about why the annotation #JsonProperty(required = true) does not work on field can be found here: Jackson #JsonProperty(required=true) doesn't throw an exception.
However there is a trick that might work for null & existing well-valued fields values but throw an exception if the field is missing completely. Create a constructor with annotation #JsonCreator (and do not create a default constructor!) where the same annotation #JsonProperty(value = "*field_name*", required = true) is used and it will throw in case of missing field, so like:
#Getter #Setter
public class KeyHolder {
private String firstKey;
private String secondKey;
private String thirdKey;
#JsonCreator
public KeyHolder(
#JsonProperty(value = "firstKey", required = true) String firstKey,
#JsonProperty(value = "secondKey", required = true) String secondKey,
#JsonProperty(value = "thirdKey", required = true) String thirdKey) {
this.firstKey = firstKey;
this.secondKey = secondKey;
this.thirdKey = thirdKey;
}
}
With these, doing:
new ObjectMapper().readValue("{ \"firstKey\": \"val1\", \"secondKey\": \"val2\" }"
, KeyHolder.class);
should result into something like:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Missing required creator property 'thirdKey' (index 2)
Any required parameter needs also to be a constructor parameter. So if there is added field fourthKey the constructor needs also a fix so like adding:
#JsonProperty(value = "fourthKey", required = true) String fourthKey) {
and
this.fourthKey = fourthKey;

Json Schema Generator with inheritance and references

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

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)

How to skip Optional.empty fields during Jackson serialization?

I have a Java class with an Optional field. I am serializing the class to JSON using Jackson 2.8.3 (called from Spring web 4.3.3).
I am hoping to get the serializer to skip the field if the Optional is empty, and serialize the contained string if it is present. An example of the result I'm looking for with a list of two objects:
[
{
"id": 1,
"foo": "bar"
},
{
"id": 2,
}
]
Here the foo Optional is empty for the object with id 2.
Instead, what I get is:
[
{
"id": 1,
"foo": {
"present": true
}
},
{
"id": 2,
"foo": {
"present": false
}
}
]
This is the result even if I annotate the "bar" field in the class like
#JsonInclude(JsonInclude.Include.NON_ABSENT)
public Optional<String> getFoo() { ...
Is there any way I can achieve a result like the first list using the Jackson annotations or a custom serializer?
No need to write custom serializer. Annotate your class with #JsonInclude(JsonInclude.Include.NON_ABSENT).
You also need to:
include com.fasterxml.jackson.datatype:jackson-datatype-jdk8 as your dependency
and to register the corresponding module with your object mapper: objectMapper.registerModule(new Jdk8Module());
You can use objectMapper.registerModule(new Jdk8Module()); but it serializes with null values.
But still you want to remove null values also from JSON, please use the following code:
objectMapper.registerModule(new Jdk8Module().configureAbsentsAsNulls(true));
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Use a JsonSerializer to your needs.
Something like this (semi-pseudo):
public class MySer extends JsonSerializer<Opional<?>> {
#Override
public void serialize(Optional<?> optString, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
//Check Optional here...
generator.writeString(/* DO SOMETHING HERE WHATEVER */);
}
//Then in your model:
public class ClassWhatever {
#JsonSerialize(using = MySer .class)
public Optional<String> getFoo() { ...
}
To avoid annotating every field with #JsonSerialize you may register your custom serializer to object mapper using
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new MyCustomSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);
Also, given solution works only for serialization. Deserialization will fail unless you write your own deserializer. Then you need to annotate every field with #JsonDeserialize or register your custom deserializer.

Saving UnknownFields to a map while deserializing Json using Jackson

My class looks like:
Class A{
private String amount;
#JsonIgnore
private Map<String,String> unknownFields = new HashMap<>();
}
My ObjectMapper have DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES = false configured.
Json input:
{
"amount": 5000,
"note" : "Amount is 5000"
}
In this case I need the note to be in the unknownFields Map:
I am looking for some annotations like
#OnUnknownProperties
public void OnUnknownProperties(String name, String value){
unknownFields.put(name,value);
}
You could annotate a Method in your Domain-Class with #JsonAnySetter (#JsonAnyGetter) and handle it. A good example is here:
http://www.jasonwhaley.com/handling-top-level-metadata-with-jackson/ . Let your DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES=false.

Categories