How does jackson serialize a map with custom getters/setters ? - java

I have a bean in jackson which uses the #JSonAnySetter method to store all unknown parameters in a map.
#JSonAnySetter
handleUnkowns(String k, Object v)
{
myMap.put(k,v);
}
I use this as the "Base bean" for all my data types, so that if data is missing, the unknown parameters are populated and data is not lost.... rather than jackson crashing.
However, I want the serialized form of these unknowns to NOT be nested - that is - I want serialized parameters to be at the top level of the object, when the object is serialized. Additionally, I want the custom fields to also be serialized :
//I want this map to be serialized/deserialized : {"collarWidth":10 "name":"fido"}
class Dog extens JSonBean
{
int collarWidth=0;
getCollarWidth(){return collarWidth;}
setCollarWidth(int x){collarWidth=x;}
}
Note that in the above case - since I extend from a Map, Jackson's custom Map serialization will take place, and the unknownParameters will be a "field" in my json.
Thus the expected JSON serialization would be
{"collarWidth":10 "unknownParameters":{"name":"fido"}}
rather than
{"collarWidth":10 "name":"fido"}
So - what is the simplest way to "merge" the unknown parameters with the known ones, so that the java bean serializer retains the same nesting as the input string ?
The obvious solution is to merge the parameters from the "myMap" object with the serialized map , but that seems like overkill, and i assume that this problem might have a more elegant solution.

Have you checked out #JsonAnyGetter annotation? Map that method returns will be unwrapped, to make it work with #JsonAnySetter. This blog entry explains usage.

Related

Serialization and deserialization of HashMap<MyObject, List<Integer> with Jackson

I am currently working on a small android app that needs to be able to save/restore a range of settings via serialization/deserialization using the Jackson lib.
For most parts it works really well and I can serialize my objects and restore them again.
Now I need to serialize/deserialize private member with the type:
HashMap<Model, List<Integer>>
Model being one of my own objects containing a bunch of simple values + getter/setter methods.
And this is where I run into problems. It starts throwing the following error at me:
DataAccess: Cannot find a (Map) Key deserializer for type [simple type, class com.schulz.toolie.Models.Model] at [Source: (String)"{"; line: 1, column: 1]
I have tried setting annotations like #JsonAnyGetter and #JsonAnySetter on the getter/setter methods along with the #JsonProperty("subscribe") on both the getter, setter and variable.
Is there any way to get around this? preferably without writing custom serialization/deserialization methods as I will get quite a few of these.
Your problem is that Jackson has a standard for converting a Map to JSON. The keys of the map are used as the property names in the results JSON.
Map<String, Double> groceryPrices = new HashMap<>();
groceryPrices.put("apple", 0.25);
groceryPrices.put("orange", 0.30);
This naturally translates to a JSON object:
{
"apple": 0.25,
"orange": 0.30
}
The problem is you are using a complex object to represent a key. There is no simple method for serializing and deserializing your complex object to/from a String.
If you don't want to write custom serialization, I suggest you change your data structure. Your current structure ties together a model with it's Integers. You could fold the list of Integers into the Model object itself:
Map<String, Model> models; // This could map modelId -> Model which now contains the integers

Serialize ArrayList<Objects> and back, preserving class

I am trying to send a collection of diffrent objects to server which accepts Json objects. Figuring out which is the optimal way to do this.
Plan A:
Serialize a collection of objects, like this:
ArrayList<Object> objects = new ArrayList<>();
objects.put(new Human("Johhny"));
objects.put(new Cannon(13));
objects.put(new Hamburger(1.3));
String json = Utils.getObjectMapper().writeValueAsString(objects);
Serialization works fine, and when I must deserialize, I receive array of Objects too (linked hash maps, to be precise). The problem is, I don't know which of these are Human, Cannon or Hamburger.
Is there any way to put object's class name into json, so when it's deserialized, the object mappers knows which class to use?
Plan B:
Parse each LinkedHashMap, manually determine it's true Class based on properties, and manually deserialize into object
Plan C:
Put each type of objects into diffrent collection with specific type.
Serialize three collections and combine them into one string with specific splitter
Deserialize back in reversed order.
The solution is:
Simply add mapper setting before writing to string: Utils.getObjectMapper().enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT).writeValueAsString(objects);
Thanks to #dmitry-zvorygin!
Polymorphic (de)serialization is all you need -
https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
Just make sure you have base class for all the entities.

converting json to java object giving wrong type of enum

I have a json which is something like following:
[{"type":"chair", "color":"red", "owners":["A","B"]},
{"type":"vase", "shape":"oval", "owners":["B", "C"]}]
As seen, both entries are different. So I need to deserialize this json and instantiate 2 objects Chair and Vase. The owners above should be List of enum in the appropriate object. The json can have many other different entries like that and in any order. So I am doing this:
List<Map<String, Object>> things = objectMapper.readValue(json,
objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
things is a list of maps and each map will correspond to one thing. I iterate over the list and from each map check 'type' and instantiate the appropriate object.
Eg:
if map.get("type") == "chair" then new Chair(map.get("color"), map.get("owners"));
Now I have an enum OWNERS. So I do this:
List<OWNERS> owners = (ArrayList<OWNERS>) map.get("owners");
I do get the list properly. But when I verify like this:
assertEquals(OWNERS.A, owners.get(0));
I get error: 'expected java.lang.OWNERS("A") but got java.lang.String("A")'
As you can see type of owners is List.
What am I missing? Am I parsing the wrong way?
Try this
assertEquals(OWNERS.A.toString(), owners.get(0));
Also, this line
List<OWNERS> owners = (ArrayList<OWNERS>) map.get("owners");
at runtime works because of type erasure. It will crash if you try to read an element of the list and cast it to OWNERS (because it will be a list of strings in fact).
Check Java generics - type erasure - when and what happens
The problem comes from the Map<String, Object>. Your object mapper can't infer the type of the owners, it takes a guess and assumes it's a String.
One thing you could do is convert the string list to a list of owners.
A better solution would be to use something like jackson-databind and annotate your Chair and Vase classes. Jackson has annotations that let it infer the type of an object from a JSON field (like your type field).
Your code should look something like this:
#JsonTypeName("chair")
#JsonTypeInfo(use=Id.NAME, include=As.PROPERTY, property="type")
public class Chair {
#JsonCreator
public Chair(#JsonProperty("color") String color,
#JsonProperty("owners") List<OWNERS> owners) { }
}
The JsonTypeInfo annotations tell jackson to use a name of your choice in the type JSON property to indentify your class. The JsonTypeName annotation specifies the name of your class.
If you don't want to bother with annotations you can use jackson's tree model. To load your json content through a Map like interface.
Using Jackson annotations was not an option for me. So I worked with the default that it considers i.e String. I did this:
List owners = (ArrayList) map.get("owners");
And now converted this to my enum by taking the corresponding enum valueOf that String:
OWNERS.valueOf(owners.get(0));

Jackson json provider linkedHashSet deserialization

We are using Spring rest template and jackson json provider to serialize/deserialize json. From my services i send a linkedHashSet back which gets converted to a HashSet on the client side when i receive it. Because of this I loose my insertion order of elements.
Is this the default implementation of jackson json provider for Set ? Is there any other way, so it can deserialize to proper implementation? I feel it's gonna be tricky but inputs will be highly appreciated from you guys.
Thanks
You can specify the concrete class for Jackson to use with the #JsonDeserialize annotation. Just put:
#JsonDeserialize(as=LinkedHashSet.class)
On the property's setter.
It all depends on what you ask the result type to be: if ask data to be mapped to a LinkedHashSet, then JSON Array gets mapped to it. If you use a vague type like java.lang.Object (or java.util.Collection), you will get ArrayList for JSON Arrays.
Keep in mind that JSON is data, not objects (by default), so metadata regarding Java types you used is not passed by default. There are ways to do that, if you need it, but usually you will simply need to provide expected type.

How can I serialise a javabean to be loaded by Spring's DataBinder?

I need to serialise a javabean (with arbitrary nested beans) to plain text, and then later restore as an Object graph.
For the purposes of this question I am not interested in XML, JSON, etc. but rather name/value pairs.
The Spring DataBinder is great for turning a list of name/value pairs into a real Object. For example we can supply:
values.addPropertyValue("id", "6789");
values.addPropertyValue("nestedBean[0].systemId", "FOO");
values.addPropertyValue("nestedBean[1].systemId", "BAR");
And use the DataBinder to produce a top level bean (with property id) containing a List of two nested beans.
My question is, how can I easily serialise a bean into the format that DataBinder expects?
I would expect something of the form:
Map<String,String> reduceBeanToNameValuePairs(Object bean)
but have had no luck finding anything of the sort.
You could have a look at Commons BeanUtils. Its BeanMap class provides an entrySet method for accessing the wrapped bean's properties as entries of a map.
This functionality is already sufficient for "flat" beans and would probably provide a good starting point for adding the necessary recursion for nested bean graphs.

Categories

Resources