Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson Json deserialization of an object to a list

I'm consuming a web service using Spring's RestTemplate and deserializing with Jackson.

In my JSON response from the server, one of the fields can be either an object or a list. meaning it can be either "result": [{}] or "result": {}.

Is there a way to handle this kind of things by annotations on the type I'm deserializing to ? define the member as an array[] or List<> and insert a single object in case of the second example ? Can I write a new HttpMessageConverter that will handle it ?

like image 759
Nati Avatar asked Feb 11 '16 12:02

Nati


1 Answers

Since you are using Jackson I think what you need is JsonDeserializer class (javadoc).

You can implement it like this:

public class ListOrObjectGenericJsonDeserializer<T> extends JsonDeserializer<List<T>> {

    private final Class<T> cls;

    public ListOrObjectGenericJsonDeserializer() {
        final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
        this.cls = (Class<T>) type.getActualTypeArguments()[0];
    }

    @Override
    public List<T> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
        final ObjectCodec objectCodec = p.getCodec();
        final JsonNode listOrObjectNode = objectCodec.readTree(p);
        final List<T> result = new ArrayList<T>();
        if (listOrObjectNode.isArray()) {
            for (JsonNode node : listOrObjectNode) {
                result.add(objectCodec.treeToValue(node, cls));
            }
        } else {
            result.add(objectCodec.treeToValue(listOrObjectNode, cls));
        }
        return result;
    }
}

...

public class ListOrObjectResultItemJsonDeserializer extends ListOrObjectGenericJsonDeserializer<ResultItem> {}

Next you need to annotate your POJO field. Let's say you have classes like Result and ResultItem:

public class Result {

    // here you add your custom deserializer so jackson will be able to use it
    @JsonDeserialize(using = ListOrObjectResultItemJsonDeserializer.class)
    private List<ResultItem> result;

    public void setResult(final List<ResultItem> result) {
    this.result = result;
    }

    public List<ResultItem> getResult() {
        return result;
    }
}

...

public class ResultItem {

    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(final String value) {
        this.value = value;
    }
}

Now you can check your deserializer:

// list of values
final String json1 = "{\"result\": [{\"value\": \"test\"}]}";
final Result result1 = new ObjectMapper().readValue(json1, Result.class);
// one value
final String json2 = "{\"result\": {\"value\": \"test\"}}";
final Result result2 = new ObjectMapper().readValue(json2, Result.class); 

result1 and result2 contain the same value.

like image 199
Kuvaldis Avatar answered Sep 20 '22 13:09

Kuvaldis