How can I configure Jackson to serialize base classes first? - java

I have some JSON schemas which exist in a hierarchy: A extends B extends C. I am generating Java classes from these using jsonschema2pojo and they get generated into a matching class hierarchy.
Because of the way I am generating the classes, I don't have fine-grained control of which annotations can be applied to which fields (i.e. #JsonPropertyOrder)
When I serialize with Jackson, I get something like
{
"propertyOfA": "razz",
"propertyOfA": "jazz",
"propertyOfA": "baz",
"propertyOfB": "bar",
"propertyOfC": "foo"
}
Which is correct since property order has no meaning in JSON. However, my actual messages are very long - thousands of characters - and when browsing the logs it would be much more convenient if the more generic attributes (those from the base schema, schema C), of which there are only a few, came first in the message.
The individual property order within a schema/class doesn't bother me so much, but it would be nice if I could get Jackson to descend the hierarchy first and then backtrack.
{
"propertyOfC": "foo",
"propertyOfB": "bar",
"propertyOfA": "razz",
"propertyOfA": "jazz",
"propertyOfA": "baz"
}
I checked all of the Features and MapperFeatures and the only thing I found to influence the order was SORT_PROPERTIES_ALPHABETICALLY.
Is there anything else I can do at the ObjectMapper-level, or otherwise without changing the class, to influence this order?

You can apply mixin annotations on a class outside of its (generated) source file. E.g.
on a new file, define an interface:
#JsonPropertyOrder({"propertyOfC", "propertyOfB"})
public interface MixinA {
}
and register it with your ObjectMapper:
objectMapper.addMixIn(A.class, MixinA.class);
properties listed in this order annotation go first so you may skip properties of A.

Related

How to Parse untyped, nested JSON with Polymorphism?

I am using Feign to hit external APIs, and then to process the externally generated JSON (aka, the response data cannot be modified in any way), and I am trying to bundle these together into an extensible super type. At this point, I am not even sure if what I am trying to do is possible with Jackson / Feign. If it would be much easier to abandon (or heavily restructure) the polymorphism, I think I am also ready to give up on it and just create a bunch of sub classes.
Here are my two main questions, with more context below.
Should I just separate the easily deduced types from the complex types, and have a little more duplicated boiler plate?
How can I create a custom deserializer for the list object I linked? Ideally I would like to have some way to populate the more boiler plate fields less manually -- as an example, it would be great if I could call default deserializers inside it, which would rely more on the standard annotations in other objects.
Ideally, I would like one class, like this:
public final class BillApiResponse {
#Valid
#JsonProperty("response_status")
private boolean responseStatus;
#Valid
#JsonProperty("response_message")
private String responseMessage;
#JsonProperty("response_data")
private BillApiResponseData responseData;
//getters and setters, etc.
}
and then I would to have Jackson automatically map the simpler objects in whatever way is easiest (LoginResponse, LoginError), while I would try to implement a custom handler for the more complex objects (UpdateObject, ListOfObjects).
So, something like this:
#JsonTypeInfo(use = Id.DEDUCTION)
#JsonSubTypes({
#Type(value = BillLoginSuccess.class),
#Type(value = BillErrorResponse.class),
//#Type(value = BillResponseObject[].class) <--- This breaks things when added
})
// #JsonTypeResolver(value = BillResponseTypeResolver.class) <--- Open to using one of
// these if I can figure out how
// #JsonDeserialize(using = BillResponseDeserializer.class) <--- Also open to using a
// custom deserializer, but I would like to keep it only for certain parts
public interface BillApiResponseData {}
Here is a link to the API specification I am trying to hit:
Get a List of Objects
This returns an untyped array of untyped objects. Jackson does not seem to like that the array is untyped, and stops parsing everything there. Once inside, we would have to grab the type from a property.
{
"response_status" : 0,
"response_message" : "Success",
"response_data" : [{
"entity" : "SentPay",
"id" : "stp01AUXGYKCBGFMaqlc"
// More fields
} // More values]
}
Login
This returns a totally new object. Generally not having issues handling this one (until I add support for the above list, and then all of the parsing breaks down as Jackson throws errors).
Update Object
This returns an untyped object. Once again, we would have to go inside and look at the property.
I have tried a number of things, but generally I was not successful (hence I am here!).
These include:
Trying to hook into the lifecycle and take over if I detect an array object. I believe this fails because Jackson throws an error when it sees the array does not have a type associated with it.
SimpleModule customDeserializerModule = new SimpleModule()
.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(
DeserializationConfig config,
BeanDescription beanDesc,
JsonDeserializer<?> defaultDeserializer) {
if (beanDesc.getBeanClass().isArray()) {
return new BillResponseDeserializer(defaultDeserializer);
} else {
return defaultDeserializer;
}
}
});
Custom Deserializers. The issue I have is that it seems to want to route ALL of my deserialization calls into the custom one, and I don't want to have to handle the simpler items, which can be deduced.
TypeIdResolvers / TypeResolvers. Frankly these are confusing me a little bit, and I cannot find a good example online to try out.

Swagger/Openapi-Annotations: How to produce allOf with $ref?

I'm generating Rest endpoints including adding Openapi/Swagger annotations to the generated code.
While it works quite well with basic types, I have some problems with custom classes.
Right now I have a lot of duplicate schema entries for the custom classes (using #Schema(implementation = MyClass.class)) but at least the needed information is there. However I'd like to find a way to remove the duplicate schema entries while retaining the additional information.
On a github-issue discussing the $ref and lack of sibling properties I found an example how you would write it manually in yaml in order to get the result I'm looking for, however I can't figure out how to set the annotations to produce it.
This is how I think the annotation should look like if I follow the example (just to be on the safe side it is added to both the getter and the setter):
import io.swagger.v3.oas.annotations.media.Schema;
...
public class SepaPaymentRequest {
...
#Schema(name = "w307BetrBeg", description = "BETRAG BEGUENSTIGTER ", allOf = { com.diesoftware.services.utils.Betrag.class }, required = true)
public void setW307BetrBeg(final Betrag w307BetrBeg) {
this.w307BetrBeg = w307BetrBeg;
}
...
}
However what I get when I fetch the openapi.yaml (snippet):
w307BetrBeg:
$ref: '#/components/schemas/Betrag'
What I'd like to have:
w307BetrBeg:
title: 'Betrag'
description: 'BETRAG BEGUENSTIGTER'
allOf:
- $ref: '#/components/schemas/Betrag'
Any hints are more than welcome.
I haven't found a way to do it using annotations, i.e. by annotating the class.
I think it's possible to do, by:
Creating a model
Injecting the model using a ModelConverter
When I say "a model" I mean an instance of io.swagger.v3.oas.models.media.Schema.
In particular I think you'd want to create and inject a io.swagger.v3.oas.models.media.ComposedSchema instance, which supports allOf.
Doing this (i.e. creating model instances) isn't very different from hand-writing the YAML.
Another possibility -- which I haven't tried -- might be to write a slightly different ModelConverter, which you install into the chain of converters. Then, intercept calls to resolve which return a SchemaObject whose name is Betrag, and (sometimes?) replace that with a ComposedSchema instance which uses allOf.

How to replace Swagger's enum with a link to a resource?

I know from the documentation that I can annotate my POJOs like this:
#ApiModelProperty(value = "pet status in the store", allowableValues = "available,pending,sold")
public String getStatus() {
return status;
}
to produce something like:
"properties": {
...,
"status": {
"type": "string",
"description": "pet status in the store",
"enum": [
"available",
"pending",
"sold"
]
}
}
Image now to implement the method:
#ApiModelProperty(value = "pets in the store")
public Set<String> getPets() {
return pets;
}
which returns a list of pets available in the store. For example, one day it could be ["cats", "dogs", "songbirds"] and then just ["cats", "dogs"] when the songbirds get sold out.
My API would in fact have an endpoint to fetch the list of pets:
http://petShop.foo/pets
Instead of using allowableValues = "cats, dogs, songbirds",
I would like to specify with a Swagger annotation that
that field must contain a value returned by the given endpoint. That is, something like:
#ApiModelProperty(value = "pets in the store", allowableValues = "/pets")
public Set<String> getPets() {...}
This in order to allow my client/front-end to know which values can be use when making a request to,
for example, buying a pet online. Exactly how I could do if I had "enum": ["cats", "dogs", ..]
You may do the following:
Fork Swagger
Extend method processAllowedValues in io.swagger.util.ParameterProcessor class to consume an Enum class in addition to comma separated values. (currently it supports only comma separated values and range)
Use your custom variant of Swagger while building your web application
However, with this method, you'll need to continue maintaining your fork of Swagger.
A Java annotation is a syntactic metadata. It gets processed during compilation and (if it has #Retention(RetentionPolicy.RUNTIME) is specified on it) is available during runtime for reflective access. Hence, there is no direct way of resolving or setting during runtime!
However, there is a way in Java to accomplish what you want - but it's a bit too complex (and uses some undocumented features!). Here is how:
Create a custom annotation ApiModelProperty(one with #Retention(RetentionPolicy.COMPILE)) - this would act as a wrapper for #ApiModelProperty
Write an annotation processor class for above annotation (it must extend from javax.annotation.processing.AbstractProcessor class)
In your annotation processor, inject #ApiModelProperty with values as read from your Enum (this part is fairly complex as you need to traverse through the AST of Enum to get allowed values)
Project Lombok is a good example. It manipulates Java's Abstract Syntax Tree to add new features in Java.
In it's source code, under lombok.javac.handlers, take a look at:
HandleConstructor.addConstructorProperties method to understand how to add annotations in compile time. (using com.sun.tools.javac.tree.JCAnnotation)
HandleVal.visitLocal method to understand how to read literal values.
You can also take a look at this tutorial: Creating Custom Transformations

Jackson - Use custom deserializer only for specific JSON

We have a class containing multiple Sets of Longs. We want them serialized as arrays, and most clients do so.
However, we have a PHP client that creates sets such that they serialize in an odd way. A set with the number 4 comes in like this:
"setOfNumbers": {
"4": 0
},
Naturally, Jackson complains about this being an object and not an array. What I would like is to have a custom deserializer that is only invoked if Jackson detects an object where a Set<Long> should be (and ideally only if they are contained in specific classes.)
I've tried this:
this.addDeserializer(Set.class, new StdDelegatingDeserializer<>(new StdConverter<Map<String, Long>, Set<Long>>() {
#Override
public Set<Long> convert(Map<String, Long> set) {
return parseLongs(set);
}
}));
The problem with this is that now it expects an object instead of an array for all Set fields. The class being deserialized is generated, so I can't add any annotations or make other changes.
If the generated class has always the same name you can try with Json Jackson Mix-in annotations as shown in this example

Order matters with class metadata in Genson - Is there a work-around?

I'm using Genson to serialize + deserialize json in my android app into polymorphic objects. The JSON is coming from a variety of sources though and I can't guarantee that the #class metadata will be the first line item in the json. Walking through the Genson code and writing test cases it looks like the #class metadata has to be the first entry in the dictionary.
Has anyone had luck working around this constraint? Is it time to switch to something else, and if so, what?
public class Message {
Payload payload;
// getters & setters
}
public abstract class Payload {
//
}
public class Notification1 extends Payload {
String text;
// getters & setters
}
public class Notification2 extends Payload {
String otherText
// getters & setters
}
String correctOrder = {"#class":"Message","payload":{"#class":"Notification1","text":"Text"}}
String modifiedOrder = {"#class":"Message","payload":{"text":"Text", "#class":"Notification1"}}
Genson g = Genson.Builder()
.addAlias("Notification1", Notification1.class)
.addAlias("Notification2", Notification2.class)
.useRuntimeType(true)
.useClassMetadata(true)
.useMetadata(true)
.useFields(false)
.useIndentation(false)
.create();
g.deserialize(correctOrder, Message.class) // This works
g.deserialize(modifiedOrder, Message.class) // This barfs with the error: com.owlike.genson.JsonBindingException: Could not deserialize to type class com.ol.communication.messages.Message
Indeed the order matters. This was choosed on purpose, see the remarks in the user guide.
If we allow the #class property anywhere in the json object, then we will have to first deserialize all the json object (and its sub properties obj/arr etc) to an intermediary data structure and then to the correct type.
This would incur additional memory overhead and less speed but greater flexibility, true.
A solution would be to mark classes that are polymorphic (annotation/config in the builder), for whom Genson would search/produce the #class property in the stream. This would allow to have this overhead only for the polymorphic objects in the stream.
At the moment it is not implemented, but I opened an issue. It will come in a future release.
Outside of the technical aspects, I don't think you should have polymorphic logic (or any other fancy stuff) when you are dealing with multiple external API. I mean this kind of features is library specific, so if you don't use the same tool on both sides you can run into troubles. Usually people have a layer that will be used to communicate with the APIs and map the data to YOUR model. If you don't own the code on both ends, I think this would be a good solution on the long term.

Categories