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

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

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

これを[Jackson](https://github.com/FasterXML/jackson "github")というありがたーーーーいライブラリを使用し、いずれにしてもあるクラスにマッピングさせようとした際の備忘録です。

## 思いつく限りでの解決策

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

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

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

```java
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<String, Object>にマッピングする

型チェックなどを行った上、自分だけで開発している 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;
 }