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" } ] }
Advertisement
Answer
You should simply annotate the parameters of your @JsonCreator
s.
@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.