Jackson Polymorphic Deserialisation (where the class depends on the JSON key) - java

TL;DR
basically, my problem is that i have a list of wrapper objects
{"stuff": [
{"foobar" : {someObjectOfTypeA}},
{"barfoo" : {someObjectOfTypeB}},
{"foobar" : {someObjectOfTypeA}}
]}
and the type of someObjectOfTypeX depends on the value of the key "foobar" or "barfoo". how can i deserialize this? (for now) serializing is not a problem.
long version
i don't know enough jackson to solve the following problem. i've tried, but i'm stuck.
the json structure i want to parse looks like this:
{
"id": "foobar",
"responses": [
{
"responseType1": {
"code": 0,
"foo": "bar"
}
},
{
"responseType2": {
"code": 1,
"bar": {"foo": ...}
}
},
{
"responseType1": {
"code": 1,
"foo": "foobar"
}
}
]
}
i tried to deserialize it using jacksons full data binding. my pojos are:
// pseudocode
// the outermost object
#JsonCreator
ServiceResponse(
#JsonProperty("id") String id,
#JsonProperty("responses") ArrayList<ResponseWrapper> responses)
// every response has a wrapper. the wrapper is an object with just one key and one value. the value is an object of a certain class (ResponseTypeX extends AResponse), and the exact ResponseType is identified by the key (the key isn't the class name though).
#JsonCreator
ResponseWrapper(AResponse keyDependsOnTypeOfAResponse ???)
// base class for all responseTypeX classes
// all subclasses of AResponse have a code and a complex payload object
#JsonCreator
AResponse (
#JsonProperty("code") int code)
// one response type
// here, the payload is just a string, in reality it's a deep structure, so i dont want to parse this manually
#JsonCreator
ResponseType1 extends AResponse (
#JsonProperty("code") int code,
#JsonProperty("foo") String foo)
// one response type
#JsonCreator
ResponseType2 extends AResponse (
#JsonProperty("code") int code,
#JsonProperty("bar") SomeOtherObject foo)
as you can see, responses is an array of wrapper objects; the "payload" class of the wrapper object is identified by the key (but the keys aren't a 1:1 match to class names). my ResponseTypeX's are limited, there are about 20 of them, so if i have to do a manual key:value type identification, i'm happy.
but is it possible to write a manual deserializer for the WrapperResponse object and continue deserializing its children with full data binding? if so, how?
i tried to just make the Wrapper accept all possible ResponseTypes as properties, hoping it would just nullify the "unset" ones, e.g.
#JsonCreator
ResponseWrapper(
#JsonProperty("responseKey1") ResponseType1 response1,
#JsonProperty("responseKey2") ResponseType2 response2,
#JsonProperty("responseKey3") ResponseType3 response3,
...)
but this failed, probably because all ResponseTypes are subclasses of AResponse and thus jackson gets confused.

Some custom deserialization processing is necessary. I'd recommend including a simple registry (map) of foobar/barfoo-to-type entries in the solution, much like the sixth example in my old blog post from May 25, 2011, "Deserialize JSON with Jackson into Polymorphic Types - A Complete Example".

Related

Is there a way to find a JSON response element value based on another element value?

Using the below method i could get the "id" element(first element in first list) value. i need to get that element value but based on the condition of another element "gifts.nameEn"
Response response = request.post("/pop/transaction/inquiry");
JsonPath jsonPathEvaluator = response.jsonPath();
List<HashMap> activeCampaignsList = jsonPathEvaluator.getList("");
for(int i=0; i< activeCampaignsList.size(); i++)
{
offerId = activeCampaignsList.get(i).get("id").toString();
break;
}
And Here's the response snippet so you would be aware of what exactly i'm looking for:
[ { "**id**": 1222, "type": "WITHIN_TRANSACTION", "gifts": [ { "startDate": "2019-06-26", "endDate": "2019-06-26", "id": 26130, "nameAr": "abcd201957261140057", "nameEn": "*abcd201957261140057*",
RestAssured allows you use JsonPath which has a strong utility in JSON processing. Instead of working with paths, you can convert JSON to Java Objects and I will show you exactly how to do it.
So, your JSON starts with an array of elements. Each of the element is JSON Object. Each of the JSON Object contains nested JSON Array gifts with JSON Objects in it. Let me explain further.
We have to process the array first, the start of your JSON [. In order to do that we can create a class representing this Array like this:
public class ElementObject { //this name is irrelevant. You can call it whatever you want
public String id; //the names of the variables are relevant
public String type; //each of variable name corresponds to the type and name in the JSON
public List<GiftObject> gifts; //here we handle the nested JSON Array of JSON Objects
}
Now, we require an object to handle gifts. That will require another class:
public class GiftObject { //irrelevant name
public String startDate; //relevant name
public String endDate;
public String id;
public String nameAr;
public String nameEn;
}
Now, all we have to do is call 1 method from JsonPath to parse whole JSON into Java classes. Like this:
response.jsonPath().getObject("", ElementObject[].class);
That's mostly it. Starting from the top:
[
{
"id":1222,
"type":"WITHIN_TRANSACTION",
We have an JSON Array with JSON Objects. 1 JSON Object equals 1 instance of ElementObject class. All of the objects from the Array will be parsed into ElementObject[].class and JsonPath will create an instance using Java Reflection (You don't have to worry about that).
"gifts": [ { "startDate": "2019-06-26", "endDate": "2019-06-26", "id": 26130, "nameAr": "abcd201957261140057", "nameEn": "abcd201957261140057",
Each of the gifts will be parsed into new instance of GiftObject and storred in List<GiftObject> in ElementObject class.
All we have to do now is work on our Java classes.
To fully answer your question - you need ID value based on gifts.nameEn
for (ElementObject element : elements) {
for (GiftObject gift : element.gifts) {
if (gift.nameEn.equals("abcd201957261140057")) {
System.out.println(element.id);
}
}
}
The above for loop can be easily replaced with a method, or Java Streams. Whatever suits you the most.
Given the above code the results will be:
1222
Hope it helps!
EDIT:
If you get the Exception like this Cannot deserialize object because of no JSON deserializer found in classpath. Please put either Jackson (Databind) or Gson in the classpath. all you have to do is add jackson-databind to Maven. Rest Assured is able to use Gson or Jackson databind to transform JSON into Java classes. All you need is the dependency, and you are all set.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>

Jackson always serialize full object or always just serialize id

I am using the annotation #JsonIdentityInfo to serialize / deserialize an object using it's id.
I implemented an ObjectIdResolver, that is working well.
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id", resolver = EntityIdResolver.class, scope = MyClass.class)
public class MyClass{....}
I found the documentation of this annotation here.
My problem is the statement: "In practice this is done by serializing the first instance as full object and object identity, and other references to the object as reference values."
Now my Rest-Endpoint return something similar to
{
"itemList": [
{
"id": 70110,
"myClass": {
"id": 3,
"name": "itemName",
"date": "2000-01-01"
},
},
{
"id": 70111,
"myClass": 3,
}
]
}
But this means, the the client has to deal with different data types. The client does know all the objects, so it does not matter if the serialization returns either allways the id only or the full object.
How do I prevent Jackson from using this behaviour?
Finally found the solution (at least for my problem) quite quickly:
Adding
#JsonIdentityReference(alwaysAsId = true)
to MyClass will allways seralize as
"myClass":3
I found this here

Deserialize a field that can be one of two data types using Moshi

I'm receiving some JSON from an OrientDB server that looks something like this:
{
...
"out": ...,
"in": ...,
...
}
Now these two fields out and in can be one of two types: String and my own custom object (let's call it a Record). For example, for one request I might receive this:
{
...
"out": "#17:0",
"in": {
...
},
...
}
For another I might get:
{
...
"out": {
...
},
"in": "#18:2",
...
}
And so on. Both might be Strings, both might be Records, one might be a String and the other a Record, et cetera et cetera. Now when I'm deserializing this kind of JSON using Moshi, I'd have two parameters out and in to hold the values of their respective keys; however, because these values aren't a fixed data type, that's easier said than done.
Creating multiple POJOs (or "POKO"s, I guess, because I'm using Kotlin) wouldn't work, because these objects can be found inside other JSON objects and stuff like that. I'd need a single object for which these parameters can take on a variable data type. So how would I do that?
Would I have to write a custom adapter in Moshi for serializing/deserializing these values? If so, how would I go about writing one that can assign a certain data type depending on the value of the parameter? Or is there some sort of Kotlin class/function/extension function I can find/write that can hold two possible data types?
If it's relevant, I'm also using Retrofit 2 + RxJava 2 to make my HTTP calls asynchronously, so if there's any data types or functions in these libraries that facilitates something like this, I'm all ears.
Even if anyone can only answer in Java that's okay, because I can convert the code myself. And if I'm missing something obvious, I apologize in advance.
You can do something like what I did in this answer: https://stackoverflow.com/a/65106419/3543610
Basically you'd create a sealed class to be the type of both your properties in and out. You'd also need to wrap the primitive string one into a type holding a String, so you can make it extend the sealed class, something like this:
sealed class YourType {
data class StringData(val value: String) : YourType()
#JsonClass(generateAdapter = true)
data class Record(
val prop1: String,
val prop2: Int
) : YourType()
}
Then your model that has these properties would look smth like:
#JsonClass(generateAdapter = true)
data class Model(
...
val in: YourType,
val out: YourType,
...
)
then finally you write your custom adapter for the YourType type:
class YourTypeCustomAdapter {
#FromJson
fun fromJson(jsonReader: JsonReader, delegate: JsonAdapter<Record>): YourType? {
return if (jsonReader.peek() == BEGIN_OBJECT) {
delegate.fromJson(jsonReader)
} else {
StringData(jsonReader.nextString())
}
}
#ToJson
fun toJson(jsonWriter: JsonWriter, yourType: YourType, delegate: JsonAdapter<Record>) {
when (yourType) {
is Record -> delegate.toJson(jsonWriter, yourType)
is StringData -> jsonWriter.value(yourType.value)
}
}
}
and register with Moshi:
private val moshi = Moshi.Builder()
.add(YourTypeCustomAdapter())
.build()

Type choice for Play Framework JSON Form

I am developing a REST webservice using the Play Framework 2.5 (Java) and the form data binding (from Spring Framework).
I am quite experienced with this API and like the way it formalizes validation constraints (e.g. Required), so I would like to avoid using the BodyParser API.
I need to parse a JSON request such as this :
{
"elements": [
{
"val": "1"
},
{
"val": ["1","2","3"]
}
]
}
The problem is that "val" accepts two different types : a string (java.lang.String in Java) and an array of strings (java.util.List in my code).
How could I "typesafely" model such a JSON form in my Java code ?
I have already tried to use an abstract (and generic) class implemented by two different subclasses with different types for the "val" attribute, but Spring fails to instantiate the object (BeanInstantiationException).
Here is the current data model :
public class Foo {
#Constraints.Required
public List<Fii> elements;
}
public class Fii {
#Constraints.Required
// Which type ? Object ?
public ? val;
}
public class Response
{
List<ResponseEntry> response;
/*getters + setters */
public static class ResponseEntry
{
private List<Value> elements;
/*setters + getters*/
public static class Value
{
private List<Object> val;
}
}
}
Unfortunately, with the structure of the JSON you are handling, the only way to deserialize it is to have the value attribute be type Object. However, once the JSON is deserialized, you can easily figure out whether value is an object or a single value.
Notice that JSON only supports five data types: objects (Map in java), arrays, strings, numeric and boolean. It looks like in your case, value would most likely be either a number or a map of numbers; then you have two possibilities to check for. Using a quick instanceof comparison, you should be able to figure out what type of value it is.
ObjectMapper mapper = new ObjectMapper();
Response r = mapper.readValues(json, Response.class);
Value val = r.response.get(0).values.get(0);
if (val.value instanceof Map)
; // multiple
else
; // single

How to identify the fields having default values when deserialized using jackson

I have a class Person and I want to deserialize a POJO from a JSON using jackson. Now,
the definition to Person class is something like :
class Person {
int id;
String name;
boolean isOldAge;
boolean hasSenseOfHumor;
.
.
.
}
Now my json is something like :
{
"id" : 1,
"isOldAge" : false
}
Now when I deserialize this into a POJO the values I will get would be :
[id=1,name="",isOldAge=false,hasSenseOfHumor=false]
i.e, the properties not mentioned in json will be assigned their default values.
So my problem lies here. Is there a way I can distinguish isOldAge from hasSenseOfHumor with respect to whether it is mentioned or provided for by the user or not.
Try to change the primitive boolean to the boxing Boolean type. The fields should be initialised with null values then.
If you cannot change field types of the class, then can read your JSON as map in advance as follows mapper.readValue(JSON, Map.class), and then reason about the presence of the boolean fields in the resulting map instance.

Categories