LoginSignup
6
6

More than 5 years have passed since last update.

JsonDeserializerを使って空の時に型が配列になるプロパティに対応する

Posted at

概要

値がある場合はオブジェクト型で空の場合は配列型のプロパティを持つ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;
    }
}
6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6