Search code examples
javajsonjacksonjackson-databind

How to write common pojo deserializer for json attribute which can be an object and an array both?


Background:

I am getting mismatchedInputException, when parsing json. com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.util.ArrayList<Data> from Object value (token JsonToken.START_OBJECT)

Problem:

nodes is a json-array which contains data, which can be either json-object or json-array .

I need to represent this correctly in POJO.

Things Tried: Map<String, Object>, List, ,@JsonDeserialize(using = NodeDeserializer.class)

Below is my nested json document for ref.

{
  "parent": {
    "nodes": [
      {
        "data": {
          "desc": "a1",
          "url": "http://a1.a11"
        }
      },
      {
        "data": [
          {
            "desc": "b1",
            "url": "http://b1.b11"
          },
          {
            "desc": "b2",
            "url": "http://b2.b21"
          }
        ]
      }
    ]
  }
}

Below is Pojo Java classes and Runner class, I am using for SER/DE for above Json

Data.java

import lombok.*;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
class Data {
    @JsonProperty("desc")
    private String desc;

    @JsonProperty("url")
    private String url;
}

Node.java

import lombok.*;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
class Node {
    @JsonProperty("data")
    //Map<String, Object> data = new LinkedHashMap<>();
    //private List<Object> data;
    private List<Data> data;
}

Parent.java

import lombok.*;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
class Parent {
    @JsonProperty("nodes")
    private List<Node> nodes;
}

Test.java

import lombok.*;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
    @JsonProperty("parent")
    private Parent parent;
}

Runner Class

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

public class TestJsonParse {
    public static void main(String[] args) throws JsonProcessingException {
        String jsonString = "{\n" +
                "  \"parent\": {\n" +
                "    \"nodes\": [\n" +
                "      {\n" +
                "        \"data\": {\n" +
                "          \"desc\": \"a1\",\n" +
                "          \"url\": \"http://a1.a11\"\n" +
                "        }\n" +
                "      },\n" +
                "      {\n" +
                "        \"data\": [\n" +
                "          {\n" +
                "            \"desc\": \"b1\",\n" +
                "            \"url\": \"http://b1.b11\"\n" +
                "          },\n" +
                "          {\n" +
                "            \"desc\": \"b2\",\n" +
                "            \"url\": \"http://b2.b21\"\n" +
                "          }\n" +
                "        ]\n" +
                "      }\n" +
                "    ]\n" +
                "  }\n" +
                "}";

        // print pojo
        ObjectMapper objectMapper = new ObjectMapper();
        Test test = objectMapper.readValue(jsonString, Test.class);
        System.out.println(test);
    }
}

Solution

  • How to write common pojo deserializer for json attribute which can be an object and an array both?

    It is possible if you use the JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature consisting of an override for the DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY feature which allows deserialization of JSON non-array values into single-element Java arrays and Collections. So you can annotate the data field of your Node class to obtain the expected behaviour:

    class Node {
        @JsonProperty("data")
        @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
        private List<Data> data;
    }