7

I have a Java object Results:

public class MetaData {
    private List<AttributeValue<String,Object>> properties
    private String name
    ...

    ... getters/setters ...
}

The AttributeValue class is a generic key-value class. It's possible different AttributeValue's are nested. The (value) Object will then be another AttributeValue and so forth.

Due to legacy reasons the structure of this object cannot be altered.

I have my JSON, which I try to map to this object. All goes well for the regular properties. Also the first level of the list is filled with AttributeValues.

The problem is the Object. Jackson doesn't know how to interpret this nested behavior and just makes it a LinkedHashMap.

I'm looking for a way to implement custom behavior to tell Jackson this has to be a AttributeValue-object instead of the LinkedHashMap.

This is how I'm currently converting the JSON:

ObjectMapper om = new ObjectMapper();
MetaData metaData = om.readValue(jsonString, new TypeReference<MetaData>(){});

And this is example JSON. (this is obtained by serializing an existing MetaData object to JSON, I have complete control over this syntax).

{
    "properties":[
        {
            "attribute":"creators",
            "value":[
                {
                    "attribute":"creator",
                    "value":"user1"
                },{
                    "attribute":"creator",
                    "value":"user2"
                }
            ]
        },{
            "attribute":"type",
            "value": "question"
        }
    ],
    "name":"example"
}

(btw: I've tried the same using GSON, but then the object is a StringMap and the problem is the same. Solutions using GSON are also welcome).

edit In Using Jackson ObjectMapper with Generics to POJO instead of LinkedHashMap there is a comment from StaxMan: "LinkedHashMap is only returned when type information is missing (or if Object.class is defined as type)."

The latter seems to be the issue here. Is there a way I can override this?

Community
  • 1
  • 1
heisa
  • 834
  • 1
  • 9
  • 17
  • Did you check this: http://stackoverflow.com/questions/22358872/how-to-convert-linkedhashmap-to-custom-java-object – Paweł Głowacz Jul 28 '15 at 07:09
  • Thanks. Yes I Did, but it's not a solution to my Issue. The MetaData object is created correctly, and there is a list of AttributeValues (on the first level). The issue is on the second level, where the _Object_ should be replaced by another (nested) AttributeValue. – heisa Jul 28 '15 at 07:15
  • In http://stackoverflow.com/questions/8903863/using-jackson-objectmapper-with-generics-to-pojo-instead-of-linkedhashmap StaxMan says: "_LinkedHashMap is only returned when type information is missing_ **(or if Object.class is defined as type)**" This seems like the cause, but I want to override this... – heisa Jul 28 '15 at 07:16
  • Can you give an example of the JSON to parse? It looks like the `AttributeValue` has a nested JSON object (i.e. `{}`) and because Jackson doesn't have any type info on the nested object, the generic deserializer kicks in. It's possible to solve this via a custom serializer but it's a little bit more work, so having an example JSON here would help. – dhke Jul 28 '15 at 07:44
  • thanks @dhke, I've added an example Json above. From what I've read I think that is indeed the way to go, but I don't really have a clue where to start. – heisa Jul 28 '15 at 07:58

1 Answers1

2

If you have control over the serialization, try calling enableDefaultTyping() on your mapper.

Consider this example:

Pair<Integer, Pair<Integer, Integer>> pair = new Pair<>(1, new Pair<>(1, 1));
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(pair);
Pair result = mapper.readValue(str, Pair.class);

Without enableDefaultTyping(), I would have str = {"k":1,"v":{"k":1,"v":1}} which would deserialize to a Pair with LinkedHashMap.

But if I enableDefaultTyping() on mapper, then str = {"k":1,"v":["Pair",{"k":1,"v":1}]} which then perfectly deserializes to Pair<Integer, Pair<...>>.

hotkey
  • 140,743
  • 39
  • 371
  • 326
  • Thanks @hotkey, I will try that. But if I understand this correctly, this will mean I have to write my Java-class into my JSON, which will couple them tighly together? – heisa Jul 28 '15 at 08:01
  • @user3319803, yes, including type info into JSON binds it to certain classes. But the binding is customizable: you can alter Jackson typing, for example, using `@JsonTypeInfo` on your class and changing mapper's typing. It can sometimes help you in dynamic/generic types deserialization even if you don't have control over the serialization, e.g. when you work with some API. – hotkey Jul 28 '15 at 08:09
  • @user3319803, by the way, you can look through [this article](http://wiki.fasterxml.com/JacksonPolymorphicDeserialization) as an introduction, though it's about polymorphic desearilzation. – hotkey Jul 28 '15 at 08:14
  • Thanks for the link @hotkey. Seems like a good introduction, and a potential solution to my problem. – heisa Jul 28 '15 at 08:21
  • I'd like for my JSON to be like this: `str = {"k":1,"v":{"k":1,"v":1}}` My JS frontend should not know about the classes of the backend. The @JsonTypeInfo seems to allow a lot of customization, but still keeps this coupling (if I understand correctly). – heisa Jul 28 '15 at 08:40
  • @user3319803, there's possible workaround in the referenced article: "Sub-class list, using `class MyPojoList extends ArrayList { }` -- type information will be retained due to inheritance ("super-type token")". So you can try to sublcass your `AttributeValue` and/or `List` containing it. – hotkey Jul 28 '15 at 10:23