概要
値がある場合はオブジェクト型で空の場合は配列型のプロパティを持つJSONを返すAPIがあり、デフォルトでは型が合わずクラスにバインドできない。
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"])
JacksonのJsonDeserializer
を使い、配列の時はnullに置き換えることで、リソースクラスにバインドできるようにする。
想定しているJSONレスポンス
値がある場合
{
"member": {
"name": "yotama"
}
}
値がない場合
{
"member": []
}
環境
- Java 8
- Jackson 2.9.4
- Lombok 1.16.20
実装
リソースクラス
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
# 簡略化の為にLombokを使用しているが、
# 1.16.20からはJacksonが使用している@ConstructorPropertiesが付与されなくなるので、暫定で設定
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;
}
}