LoginSignup
0
1

More than 1 year has passed since last update.

Jacksonで複数の型を取りうるJSONの値を特定クラスにマッピングする

Posted at

JSONは型が自由

JSONはJavaScriptのオブジェクトっぽい、キーバリューでのデータの表現方法なわけですが、その型には、オブジェクト、配列、文字列、数値、true、false、nullが存在してます。オブジェクトの構造は自由、配列内の型も固定されているわけではないので、以下のようなデータも存在します(良し悪しはおいておいて)。

{
  "hoge" : ["string", ["array1", "array2"], {"name" : "三雲修", "code" : "B-2-1"}]
}

これをJacksonというありがたーーーーいライブラリを使用し、いずれにしてもあるクラスにマッピングさせようとした際の備忘録です。

思いつく限りでの解決策

扱う以前にそもそもこの形式のデータに相対したときにいくつかのアプローチが考えられるかと思います。

(1) 全部オブジェクトにしちゃう

{
 "hoge" : [{"stringValue" : "string"}, {"arrayValue" : ["array1", "array2"]}, {"name" : "三雲修", "code" : "B-2-1"}]
public class MyJson {
   public Hoge hoge;
}

public class Hoge {
  public String stringValue;
  public List<String> arrayValue;
  public String name;
  public String code;
}

これならおそらくMyJsonに対してマッピング可能なのではないでしょうか試してないけど

今回は自分でスキーマを定義していますが、APIなど他者が作ったスキーマの場合もあるのでこの手段がとれないこともあります。
また、JSON側に本質的な意味合いでないキー名をつけなくてはいけない、重複が許されない、という問題もありますね。要するにJSON作る側が手間になるかも、というところ。機械的に生成する上、拡張をあまり考えなくていいのならこれでもいいかもしれません。

(2) Mapにマッピングする

型チェックなどを行った上、自分だけで開発している or 機能が局所化している or ゴリゴリに何が来るかわからないJSONならよさそうです。

(3) JsonNodeのまま扱う

これも(2)と同じ感じで、よりrawに近い感じですかね。
JacksonのAPIについての知識(というほどでもないですが)が要求されるので一手間かかる一方、やれることは増えます。

・・・と、やり口はきっと他にもあるとは思うのですが、今回はPOJOにマッピングします。

JsonDeserializerを使う(マッピング先がListの場合は失敗)

json内の特定のキーの値に応じてであればCreatorを指定する方法でいけそうです。
このJsonはそうではないので、JsonDeserializerを使って実現します。

Hoge.java
@Data
@JsonDeserialize(using = MultiJsonDeserializer.class)
public class Hoge {

    // プロパティをそれぞれ用意しておく 
    // 必須ではないがコンストラクタも用意
    private String a;
    Hoge(String a) {
        this.a = a;
    }

    private List<String> b;
    Hoge(List<String> b) {
        this.b = b;
    }

    private ItemDto c;
    Hoge(ItemDto c) {
        this.c = c;
    }

    // Deserializerを継承したクラスを作る
    public static class  HogeJsonSerializer extends StdDeserializer<Hoge> {

        protected HogeJsonDeserializer() {
            this(null);
        }

        protected HogeJsonDeserializer(Class<?> vc) {
            super(vc);
        }

        @Override
        public Hoge deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException, JsonProcessingException {
            JsonNode node = p.getCodec().readTree(p);
            if (node == null) {
                return null;
            }
            Hoge hoge;
            // Nodeごとにうまいことアレする
            if (node.isArray()) {
                List<String> strs = new ArrayList<>();
                Iterator<JsonNode> ite = node.iterator();
                while(ite.hasNext()) {
                    JsonNode element = ite.next();
                    if (element.isTextual()) {
                        strs.add(element.asText());
                    }
                }
                hoge = new Hoge(strs);
           } else if (node.isTextual()) {
               hoge = new Hoge(node.asText());
           } else if (node.isObject()) {
               hoge = new Hoge(p.readValueAs(Something.class)); // 別のマッピングを行う
           } else {
               hoge = null;
           }
            return hoge;
        }
    }
 } 
MyJson.java
 @Data
 public class MyJson {
     private List<Hoge> hoge;
 }

これでよしーと思ったらうまくいかない。無限ループになってOutOfMemoryErrorになる。

Test.java
// NG!!!!
   @Test
    public void test() throws Exception {
        final String myJson = "{\"hoge\" : [\"mojiretu\", [\"array1\", \"array2\"], {\"code\" : \"123\", \"name\" : \"myName\"}]}";
        ObjectMapper om = new ObjectMapper().readerFor(Hoge.class).readValue(myJson);
    }

Listではなく、単位値であればうまくいきました。

MyJson.java
 @Data
 public class MyJson {
     private Hoge hoge; 
 }
Test.java
@Test
    public void test() throws Exception {
        String myJson = "{\"hoge\" : \"mojiretsu\"}"; // 文字列
        ObjectMapper om = new ObjectMapper().readerFor(Hoge.class).readValue(myJson);
        myJson = "{\"hoge\" : [\"array1\", \"array2\"]}"; // 配列
        om = new ObjectMapper().readerFor(Hoge.class).readValue(myJson);
        myJson = "{\"hoge\" : {\"name\" : \"三雲修\", \"code\" : \"B-2-1\"]}"; // オブジェクト
        om = new ObjectMapper().readerFor(Hoge.class).readValue(myJson);
    }

Listに対してマッピングする場合

アノテーションつけたらうまくいきました。よかった

MyJson.java
 @Data
 public class MyJson {
     @JsonDeserialize(contentUsing = HogeJsonDeserializer.class) // これつけたらいけた
     private List<Hoge> hoge;
 }


0
1
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
0
1