Java ライブラリである Jackson の @JsonProperty アノテーションを使って JSON をパースしてオブジェクト化する

## 概要

- Java の JSON ライブラリである Jackson を使う
- いくつかの書き方で JSON 文字列をパースしてオブジェクト化してみる
- @ JsonProperty アノテーションを使ってオブジェクト化する
- 比較用にアノテーションを使わない書き方もやってみる

## 今回のパース対象の JSON

```json
{
  "user": {
    "name": "Pompom Pudding",
    "score": {
      "kokugo": "good",
      "sansuu": 100
    },
    "favorites": ["soccer", "puzzle"]
  }
}
```

## いくつかの書き方で JSON をパースしてオブジェクト化する

### クラスを用意せずに Map オブジェクトを直接生成する場合 (アノテーション不使用)

ObjectMapper#readValue の第二引数に Map.class を渡せば Map オブジェクトを取得できる。
オブジェクトを生成するのはシンプルだが、生成されたオブジェクトから目的のデータを取り出すのが複雑になる。

```java
ObjectMapper mapper = new ObjectMapper();
Map obj = mapper.readValue(json, Map.class);
System.out.println(obj);
String name = (String)(((Map)obj.get("user")).get("name"));
System.out.println("name=" + name);
```

出力例。

```
{user={name=Pompom Pudding, score={kokugo=good, sansuu=100}, favorites=[soccer, puzzle]}}
name=Pompom Pudding
```

### クラスに public なインスタンス変数を用意する場合 (アノテーション不使用)

public なインスタンス変数、または getter setter を用意することでオブジェクト化することが可能になる。
その場合 @ JsonProperty アノテーションを記述する必要は無い。

```java
import java.util.Map;
public class PublicUserObject {
  public Map user;
}
```

```java
ObjectMapper mapper = new ObjectMapper();
PublicUserObject obj = mapper.readValue(json, PublicUserObject.class);
System.out.println(obj);
String name = (String)obj.user.get("name");
System.out.println("name=" + name);
```

出力例。

```
PublicUserObject@e6abefac
name=Pompom Pudding
```

### クラスに private なインスタンス変数を用意する場合 (アノテーション使用)

private なインスタンス変数を使う場合は @ JsonProperty にてマッピングしたい JSON のキー(名前)を指定する必要がある。

```java
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;

public class SimpleUserObject {

  @JsonProperty("user")
  private Map user;

  public Map getUser() {
    return user;
  }

  @Override
  public String toString() {
    return getClass().getName() + ": user:" + user;
  }
}
```

```java
ObjectMapper mapper = new ObjectMapper();
SimpleUserObject obj = mapper.readValue(json, SimpleUserObject.class);
System.out.println(obj);
String name = (String) obj.getUser().get("name");
System.out.println("name=" + name);
```

出力例。

```
SimpleUserObject: user:{name=Pompom Pudding, score={kokugo=good, sansuu=100}, favorites=[soccer, puzzle]}
name=Pompom Pudding
```

### クラスに private なインスタンス変数とアクセス用のメソッドを用意した場合 (アノテーション使用)

もう少ししっかりアクセサメソッドを作っておくと外部のクラスから使いやすくなる。

```java
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;

public class AccessorUserObject {

  @JsonProperty("user")
  private Map user;

  public String getName() {
    return (String) user.get("name");
  }

  private Map getScore() {
    return (Map) user.get("score");
  }

  public String getKokugoScore() {
    return (String) getScore().get("kokugo");
  }

  public int getSansuuScore() {
    return (int) getScore().get("sansuu");
  }

  public List getFavorites() {
    return (List) user.get("favorites");
  }

  @Override
  public String toString() {
    return getClass().getName() +
        ", getName: " + getName() +
        ", getKokugoScore: " + getKokugoScore() +
        ", getSansuuScore: " + getSansuuScore() +
        ", getFavorites: " + getFavorites();
  }
}
```

```java
ObjectMapper mapper = new ObjectMapper();
AccessorUserObject obj = mapper.readValue(json, AccessorUserObject.class);
System.out.println(obj);
String name = obj.getName();
System.out.println("name=" + name);
```

出力例。

```
AccessorUserObject, getName: Pompom Pudding, getKokugoScore: good, getSansuuScore: 100, getFavorites: [soccer, puzzle]
name=Pompom Pudding
```

### マッピング用のクラスを用意した場合 (アノテーション使用)

JSON データをマッピングするためのクラスと内部クラスを用意する。

```java
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;

public class StructedUserObject {

  @JsonProperty("user")
  public User user;

  public static class User {

    public String name;
    public Score score;
    public List favorites;

    @Override
    public String toString() {
      return getClass().getSimpleName()
          + ": name: " + name
          + ", score: " + score
          + ", favorites: " + favorites;
    }
  }

  public static class Score {

    public String kokugo;
    public int sansuu;

    @Override
    public String toString() {
      return getClass().getSimpleName()
          + ": kokugo: " + kokugo
          + ", sansuu: " + sansuu;
    }
  }

  @Override
  public String toString() {
    return getClass().getName() + ": user:" + user;
  }
}
```

```java
ObjectMapper mapper = new ObjectMapper();
StructedUserObject obj = mapper.readValue(json, StructedUserObject.class);
System.out.println(obj);
String name = obj.user.name;
System.out.println("name=" + name);
```

出力例。

```
StructedUserObject: user:User: name: Pompom Pudding, score: Score: kokugo: good, sansuu: 100, favorites: [soccer, puzzle]
name=Pompom Pudding
```

## 今回の動作確認に使ったファイルと実行環境

### Java と Maven

```
$ mvn --version
Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-05T04:00:29+09:00)
Maven home: /usr/local/Cellar/maven/3.6.1/libexec
Java version: 11.0.2, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.14.5", arch: "x86_64", family: "mac"
```

### ファイル一覧

```
├── pom.xml
├── src
│   └── main
│       └── java
│           └── info
│               └── maigo
│                   └── lab
│                       └── myjacksonsamples
│                           ├── AccessorUserObject.java
│                           ├── Main.java
│                           ├── PublicUserObject.java
│                           ├── SimpleUserObject.java
│                           └── StructedUserObject.java
└── user.json
```

### pom.xml

Maven でビルド＆実行できるように pom.xml ファイルを用意する。
dependencies に Jackson ライブラリ jackson-core, jackson-databind, jackson-annotations を追加する。
Maven から Java プログラムを実行できるように plugins に exec-maven-plugin を追加する。

```
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>info.maigo.lab</groupId>
  <artifactId>my-jackson-samples</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <!-- $ mvn exec:java -->
  <!-- https://www.mojohaus.org/exec-maven-plugin/ -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <configuration>
          <mainClass>info.maigo.lab.myjacksonsamples.Main</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>

    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.9.9</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.9</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.9</version>
    </dependency>

  </dependencies>

</project>
```

JAR ファイルを生成するコマンド。

```
$ mvn package
```

サンプルプログラムを実行するコマンド。

```
$ mvn exec:java
```

### Java ソースコード

#### Main.java

```java
package info.maigo.lab.myjacksonsamples;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        String json = Files.readString(Path.of("user.json"), StandardCharsets.UTF_8);
        createMappedUserObject(json);
        createPublicUserObject(json);
        createSimpleUserObject(json);
        createAccessorUserObject(json);
        createStructedUserObject(json);
    }

    private static void createMappedUserObject(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Map obj = mapper.readValue(json, Map.class);
        System.out.println(obj);
        String name = (String) (((Map) obj.get("user")).get("name"));
        System.out.println("name=" + name);
    }

    private static void createPublicUserObject(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        PublicUserObject obj = mapper.readValue(json, PublicUserObject.class);
        System.out.println(obj);
        String name = (String) obj.user.get("name");
        System.out.println("name=" + name);
    }

    private static void createSimpleUserObject(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        SimpleUserObject obj = mapper.readValue(json, SimpleUserObject.class);
        System.out.println(obj);
        String name = (String) obj.getUser().get("name");
        System.out.println("name=" + name);
    }

    private static void createAccessorUserObject(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        AccessorUserObject obj = mapper.readValue(json, AccessorUserObject.class);
        System.out.println(obj);
        String name = obj.getName();
        System.out.println("name=" + name);
    }

    private static void createStructedUserObject(String json) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        StructedUserObject obj = mapper.readValue(json, StructedUserObject.class);
        System.out.println(obj);
        String name = obj.user.name;
        System.out.println("name=" + name);
    }
}
```

#### PublicUserObject.java

```java
package info.maigo.lab.myjacksonsamples;

import java.util.Map;

public class PublicUserObject {

    public Map user;

}
```

#### SimpleUserObject.java

```java
package info.maigo.lab.myjacksonsamples;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Map;

public class SimpleUserObject {

    @JsonProperty("user")
    private Map user;


    public Map getUser() {
        return user;
    }

    @Override
    public String toString() {
        return getClass().getName() + ": user:" + user;
    }
}
```

#### AccessorUserObject.java

```java
package info.maigo.lab.myjacksonsamples;

import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.annotation.JsonProperty;

public class AccessorUserObject {

    @JsonProperty("user")
    private Map user;

    public String getName() {
        return (String) user.get("name");
    }

    private Map getScore() {
        return (Map) user.get("score");
    }

    public String getKokugoScore() {
        return (String) getScore().get("kokugo");
    }

    public int getSansuuScore() {
        return (int) getScore().get("sansuu");
    }

    public List getFavorites() {
        return (List) user.get("favorites");
    }

    @Override
    public String toString() {
        return getClass().getName() +
                ", getName: " + getName() +
                ", getKokugoScore: " + getKokugoScore() +
                ", getSansuuScore: " + getSansuuScore() +
                ", getFavorites: " + getFavorites();
    }
}
```

#### StructedUserObject.java

```java
package info.maigo.lab.myjacksonsamples;

import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;

public class StructedUserObject {

    @JsonProperty("user")
    public User user;

    public static class User {

        public String name;
        public Score score;
        public List favorites;

        @Override
        public String toString() {
            return getClass().getSimpleName()
                    + ": name: " + name
                    + ", score: " + score
                    + ", favorites: " + favorites;
        }
    }

    public static class Score {

        public String kokugo;
        public int sansuu;

        @Override
        public String toString() {
            return getClass().getSimpleName()
                    + ": kokugo: " + kokugo
                    + ", sansuu: " + sansuu;
        }
    }

    @Override
    public String toString() {
        return getClass().getName() + ": user:" + user;
    }
}
```

### user.json

```json
{
  "user": {
    "name": "Pompom Pudding",
    "score": {
      "kokugo": "good",
      "sansuu": 100
    },
    "favorites": ["soccer", "puzzle"]
  }
}
```

## 参考資料

- [FasterXML/jackson: Main Portal page for the Jackson project](https://github.com/FasterXML/jackson)
- [Jackson Annotations · FasterXML/jackson\-annotations Wiki](https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations)
- [Jackson使い方メモ \- Qiita](https://qiita.com/opengl-8080/items/b613b9b3bc5d796c840c)
- [niwasawa/java\-jackson\-samples: Sample codes of Jackson, JSON library for Java\.](https://github.com/niwasawa/java-jackson-samples)