lunatikz lunatikz - 4 days ago 5
Java Question

Json Deserialization in Java /w Jackson of mixed types, contained in one array

Consider the following json, getting from an public API:

anyObject : {
attributes: [
{
"name":"anyName",
"value":"anyValue"
},
{
"name":"anyName",
"value":
{
"key":"anyKey",
"label":"anyLabel"
}
}
]
}


As you can see, sometimes the value is a simple string and sometimes its an object. Is it somehow possible to deserialize those kind of json-results, to something like:

class AnyObject {
List<Attribute> attributes;
}

class Attribute {
private String key;
private String label;
}


How would I design my model to cover both cases. Is that possible ?

Answer

Despite being hard to manage as others have pointed out, you can do what you want. Add a custom deserializer to handle this situation. I rewrote your beans because I felt your Attribute class was a bit misleading. The AttributeEntry class in the object that is an entry in that "attributes" list. The ValueObject is the class that represents that "key"/"label" object. Those beans are below, but here's the custom deserializer. The idea is to check the type in the JSON, and instantiate the appropriate AttributeEntry based on its "value" type.

public class AttributeDeserializer extends JsonDeserializer<AttributeEntry> {
    @Override
    public AttributeEntry deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode root = p.readValueAsTree();
        String name = root.get("name").asText();
        if (root.get("value").isObject()) {
            // use your object mapper here, this is just an example
            ValueObject attribute = new ObjectMapper().readValue(root.get("value").asText(), ValueObject.class);
            return new AttributeEntry(name, attribute);
        } else if (root.get("value").isTextual()) {
            String stringValue = root.get("value").asText();
            return new AttributeEntry(name, stringValue);
        } else {
            return null; // or whatever
        }
    }
}

Because of this ambiguous type inconvenience, you will have to do some type checking throughout your code base.

You can then add this custom deserializer to your object mapper like so:

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(AttributeEntry.class, new AttributeDeserializer());
objectMapper.registerModule(simpleModule);

Here's the AttributeEntry:

public class AttributeEntry {
    private String name;
    private Object value;

    public AttributeEntry(String name, String value) {
        this.name = name;
        this.value = value;
    }

    public AttributeEntry(String name, ValueObject attributes) {
        this.name = name;
        this.value = attributes;
    }
    /* getters/setters */
}

Here's the ValueObject:

public class ValueObject {
    private String key;
    private String label;
    /* getters/setters */
}
Comments