Home > Enterprise >  Jackson throws error mapping exception when I try to deserialize a list of objects
Jackson throws error mapping exception when I try to deserialize a list of objects

Time:12-09

I try to deserialize a huge API payload. This payload contains way more fields than I need and therefore I am utilizing @JsonIgnoreProperties(ignoreUnknown = true). However at some point the deserialization fails with the error message:

com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of FIELD_NAME token
 at [Source: {
  "objectEntries": [
    {
      "objectKey": "KDS-4300"
    },
    {
      "objectKey": "KDS-4327"
    }
  ]
}; line: 2, column: 3]

I found solutions for that case that suggested the use of

objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

I tried this. But it did not help. Also my result data is not a single valued array. It actually contains two values - therefore the solution doesn't add up anyway.

Here are my classes that are the target of the deserialization.

@JsonIgnoreProperties(ignoreUnknown = true)
public class InsightQueryResult {
    @JsonProperty("objectEntries")
    private List<ObjectEntry> objectEntries;

    @JsonCreator
    public InsightQueryResult(List<ObjectEntry> objectEntries) {
        this.objectEntries = objectEntries;
    }

    public List<ObjectEntry> getObjectEntries() {
        return objectEntries;
    }

    // equals, hashCode and toString
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class ObjectEntry {
    @JsonProperty("objectKey")
    private String objectKey;

    @JsonCreator
    public ObjectEntry(String objectKey) {
        this.objectKey = objectKey;
    }

    public String getObjectKey() {
        return objectKey;
    }
 // equals, hashCode and toString
}

Here is the unit test where I test it out:


    @Test
    public void shouldMapQueryResultToResultObject() throws IOException {
        final Resource expectedQueryResult= new ClassPathResource("testQueryPayload.json");
        final String expectedQueryResultData = new String(
                Files.readAllBytes(expectedQueryResult.getFile().toPath())).trim();

        final List<ObjectEntry> objectEntries = Arrays.asList(new ObjectEntry("KDS-4300"), new ObjectEntry("KD-4327"));
        final InsightQueryResult expectedQueryResult = new InsightQueryResult(objectEntries);

        final InsightQueryResult result = objectMapper.readValue(expectedQueryResultData, InsightQueryResult.class);

        assertThat(result).isEqualTo(expectedQueryResult);
    }

And here is the payload I want to deserialize

// testQueryPayload.json
{
  "objectEntries": [
    {
      "objectKey": "KDS-4300"
    },
    {
      "objectKey": "KDS-4327"
    }
  ]
}

CodePudding user response:

You should simply annotate the parameters of your @JsonCreators.

@JsonCreator
public ObjectEntry(String objectKey) {
    this.objectKey = objectKey;
}

becomes

@JsonCreator
public ObjectEntry(@JsonProperty(value = "objectKey", required = true) String objectKey) {
    this.objectKey = objectKey;
}

and same goes for the other constructor.

Explanation:

  • Serialization: you have an instance with fields, you annotate a field with @JsonProperty("name") (or the getter with @JsonValue) and that allows Jackson to build a string json from your instance by reflection.
  • Deserialization: following the same logic, when you annotate a constructor with @JsonCreator you're telling Jackson this is the constructor they should use to build your object from the Json string they have. However, either you give them an empty constructor (and then by reflection they will set each field later), or you have to tell them which field of the Json string they have to use inside each constructor parameter.
  • Related