There is an API that returns JSON with properties of object type if there is a value and array type if it is empty, and by default the types do not match and cannot be bound to the class.
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `net.yotama.sample.resource.Resource$Member` out of START_ARRAY token
at [Source: (String)"{"member": []}"; line: 1, column: 12](through reference chain: net.yotama.sample.resource.Resource["member"])
Use Jackson's JsonDeserializer
and replace it with null when it is an array so that it can be bound to the resource class.
If there is a value
{
"member": {
"name": "yotama"
}
}
If there is no value
{
"member": []
}
Resource.java
package net.yotama.sample.resource;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Value;
import java.io.IOException;
@Value
public class Resource {
@JsonDeserialize(using = MemberDeserializer.class)
Member member;
@Value
public static class Member {
String name;
}
public static class MemberDeserializer extends JsonDeserializer<Member> {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public Member deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TreeNode treeNode = p.getCodec().readTree(p);
return treeNode.isObject() ? objectMapper.readValue(treeNode.toString(), Member.class) : null;
}
}
}
lombok.config
#I'm using Lombok for simplicity,
# 1.16.Used by Jackson from 20@ConstructorProperties will not be assigned, so set it provisionally
lombok.anyConstructor.addConstructorProperties=true
package net.yotama.sample.resource;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@RunWith(Theories.class)
public class ResourceTest {
private ObjectMapper objectMapper = new ObjectMapper();
@DataPoints
public static Fixture[] data = {
new Fixture("{\"member\": []}", new Resource(null)),
new Fixture("{\"member\": null}", new Resource(null)),
new Fixture("{\"member\": {\"name\": \"hoge\"}}", new Resource(new Resource.Member("hoge"))),
};
@Theory
public void test(Fixture fixture) throws IOException {
assertThat(objectMapper.readValue(fixture.json, Resource.class), is(fixture.expected));
}
@RequiredArgsConstructor
public static class Fixture {
final String json;
final Resource expected;
}
}
Recommended Posts