1. 序章

このチュートリアルでは、 Moshi を見ていきます。これは、Java用の最新のJSONライブラリであり、コード内で強力なJSONシリアル化と逆シリアル化を簡単に行うことができます。

MoshiのAPIは、機能を損なうことなく、JacksonやGsonなどの他のライブラリよりも小さくなっています。 これにより、アプリケーションへの統合が容易になり、よりテスト可能なコードを記述できるようになります。 また、依存関係が小さいため、Android向けの開発など、特定のシナリオで重要になる場合があります。

2. ビルドにMoshiを追加する

使用する前に、まずMoshiJSON依存関係pom.xmlファイルに追加する必要があります。

<dependency>
    <groupId>com.squareup.moshi</groupId>
    <artifactId>moshi</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>com.squareup.moshi</groupId>
    <artifactId>moshi-adapters</artifactId>
    <version>1.9.2</version>
</dependency>

com.squareup.moshi:moshi 依存関係はメインライブラリであり、 com.squareup.moshi:moshi-adapters依存関係はいくつかの標準タイプのアダプターです。詳細は後で説明します。

3. MoshiとJSONの操作

Moshiを使用すると、Javaの値をJSONに変換し、理由を問わず、必要な場所に戻すことができます。 ファイルストレージ、REST APIの作成など、必要なものは何でも。

Moshiは、JsonAdapterクラスの概念で動作します。 これは、特定のクラスをJSON文字列にシリアル化し、JSON文字列を正しいタイプに逆シリアル化するためのタイプセーフなメカニズムです。

public class Post {
    private String title;
    private String author;
    private String text;
    // constructor, getters and setters
}

Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

JsonAdapter を作成したら、 toJson()メソッドを使用して値をJSONに変換するために、必要なときにいつでも使用できます。

Post post = new Post("My Post", "Baeldung", "This is my post");
String json = jsonAdapter.toJson(post);
// {"author":"Baeldung","text":"This is my post","title":"My Post"}

そしてもちろん、対応する fromJson()メソッドを使用して、JSONを期待されるJavaタイプに変換し直すことができます。

Post post = jsonAdapter.fromJson(json);
// new Post("My Post", "Baeldung", "This is my post");

4. 標準のJavaタイプ

Moshiには、標準のJavaタイプのサポートが組み込まれており、期待どおりにJSONとの間で変換を行います。 これは以下をカバーします:

これらに加えて、Moshiは任意のJava Beanを自動的に処理し、これをJSONオブジェクトに変換します。このオブジェクトでは、他のタイプと同じルールを使用して値が変換されます。 これは明らかに、JavaBean内のJavaBeanが必要な深さまで正しくシリアル化されていることを意味します。

moshi-adapters 依存関係により、次のようないくつかの追加の変換ルールにアクセスできます。

  • Enum用のもう少し強力なアダプター–JSONから不明な値を読み取るときのフォールバック値をサポートします
  • RFC-3339形式をサポートするjava.util.Date用のアダプター

これらのサポートは、使用する前にMoshiインスタンスに登録する必要があります。 独自のカスタムタイプのサポートを追加すると、すぐにこの正確なパターンが表示されます。

Moshi moshi = new Moshi.builder()
  .add(new Rfc3339DateJsonAdapter())
  .add(CurrencyCode.class, EnumJsonAdapter.create(CurrencyCode.class).withUnknownFallback(CurrencyCode.USD))
  .build()

5. モシのカスタムタイプ

これまでのすべてのことで、JavaオブジェクトをJSONにシリアル化および逆シリアル化するための完全なサポートが提供されています。 ただし、これでは、JSONがどのように見えるかをあまり制御できず、オブジェクトのすべてのフィールドをそのまま文字通り書き込むことでJavaオブジェクトをシリアル化します。 これは機能しますが、必ずしも私たちが望むものではありません。

代わりに、独自のタイプ用に独自のアダプターを作成し、これらのタイプのシリアル化と逆シリアル化がどのように機能するかを正確に制御できます。

5.1. 単純な変換

単純なケースは、JavaタイプとJSONタイプ(たとえば、文字列)の間で変換することです。 これは、複雑なデータを特定の形式で表す必要がある場合に非常に役立ちます。

たとえば、投稿の作成者を表すJavaタイプがあるとします。

public class Author {
    private String name;
    private String email;
    // constructor, getters and setters
}

何の努力もせずに、これはnameemailの2つのフィールドを含むJSONオブジェクトとしてシリアル化されます。 ただし、名前と電子メールアドレスを組み合わせて、単一の文字列としてシリアル化します。

これを行うには、@ToJsonで注釈が付けられたメソッドを含む標準クラスを記述します。

public class AuthorAdapter {
    @ToJson
    public String toJson(Author author) {
        return author.name + " <" + author.email + ">";
    }
}

もちろん、私たちは逆の方向にも進む必要があります。 文字列を解析してAuthorオブジェクトに戻す必要があります。 これは、代わりに@FromJsonで注釈が付けられたメソッドを追加することによって行われます。

@FromJson
public Author fromJson(String author) {
    Pattern pattern = Pattern.compile("^(.*) <(.*)>$");
    Matcher matcher = pattern.matcher(author);
    return matcher.find() ? new Author(matcher.group(1), matcher.group(2)) : null;
}

完了したら、これを実際に利用する必要があります。 これは、 Moshi を作成するときに、Moshi.Builderにアダプターを追加して行います。

Moshi moshi = new Moshi.Builder()
  .add(new AuthorAdapter())
  .build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

これで、これらのオブジェクトをJSONとの間ですぐに変換し始め、必要な結果を得ることができます。

Post post = new Post("My Post", new Author("Baeldung", "[email protected]"), "This is my post");
String json = jsonAdapter.toJson(post);
// {"author":"Baeldung <[email protected]>","text":"This is my post","title":"My Post"}

Post post = jsonAdapter.fromJson(json);
// new Post("My Post", new Author("Baeldung", "[email protected]"), "This is my post");

5.2. 複雑な変換

これらの変換は、JavaBeanとJSONプリミティブタイプの間で行われました。 構造化されたJSONに変換することもできます。基本的に、JSONでレンダリングするためにJavaタイプを別の構造に変換できます。

たとえば、日付/時刻の値を、日付、時刻、タイムゾーンの3つの異なる値としてレンダリングする必要がある場合があります。

Moshiを使用して行う必要があるのは、目的の出力を表すJavaタイプを記述することだけです。次に、 @ToJson メソッドがこの新しいJavaオブジェクトを返すことができ、Moshiは標準のルールを使用してJSONに変換します。

public class JsonDateTime {
    private String date;
    private String time;
    private String timezone;

    // constructor, getters and setters
}
public class JsonDateTimeAdapter {
    @ToJson
    public JsonDateTime toJson(ZonedDateTime input) {
        String date = input.toLocalDate().toString();
        String time = input.toLocalTime().toString();
        String timezone = input.getZone().toString();
        return new JsonDateTime(date, time, timezone);
    }
}

予想どおり、逆の方法は、新しいJSON構造化タイプを取得して目的のタイプを返す@FromJsonメソッドを作成することで実行されます。

@FromJson
public ZonedDateTime fromJson(JsonDateTime input) {
    LocalDate date = LocalDate.parse(input.getDate());
    LocalTime time = LocalTime.parse(input.getTime());
    ZoneId timezone = ZoneId.of(input.getTimezone());
    return ZonedDateTime.of(date, time, timezone);
}

次に、これを上記とまったく同じように使用して、ZonedDateTimeを構造化された出力に変換して元に戻すことができます。

Moshi moshi = new Moshi.Builder()
  .add(new JsonDateTimeAdapter())
  .build();
JsonAdapter<ZonedDateTime> jsonAdapter = moshi.adapter(ZonedDateTime.class);

String json = jsonAdapter.toJson(ZonedDateTime.now());
// {"date":"2020-02-17","time":"07:53:27.064","timezone":"Europe/London"}

ZonedDateTime now = jsonAdapter.fromJson(json);
// 2020-02-17T07:53:27.064Z[Europe/London]

5.3. 代替タイプのアダプター

フィールドのタイプに基づくのではなく、単一のフィールドに代替アダプターを使用したい場合があります。

たとえば、ISO-8601文字列ではなく、エポックからのミリ秒として日付と時刻をレンダリングする必要がある単一のケースがある場合があります。

Moshiでは、特別に注釈が付けられた注釈を使用してこれを行うことができます。この注釈は、フィールドとアダプターの両方に適用できます。

@Retention(RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@JsonQualifier
public @interface EpochMillis {}

これの重要な部分は、 @JsonQualifier アノテーションです。これにより、Moshiは、これでアノテーションが付けられたフィールドを適切なアダプターメソッドに関連付けることができます。

次に、アダプタを作成する必要があります。 いつものように、タイプとJSONを変換するための@FromJsonメソッドと@ToJsonメソッドの両方があります。

public class EpochMillisAdapter {
    @ToJson
    public Long toJson(@EpochMillis Instant input) {
        return input.toEpochMilli();
    }
    @FromJson
    @EpochMillis
    public Instant fromJson(Long input) {
        return Instant.ofEpochMilli(input);
    }
}

ここでは、@ToJsonメソッドへの入力パラメーターと@FromJsonメソッドの戻り値にアノテーションを使用しました。

Moshiは、このアダプターまたは@EpochMillisで注釈が付けられている任意のフィールドを使用できるようになりました。

public class Post {
    private String title;
    private String author;
    @EpochMillis Instant posted;
    // constructor, getters and setters
}

これで、注釈付きタイプをJSONに変換し、必要に応じて元に戻すことができます。

Moshi moshi = new Moshi.Builder()
  .add(new EpochMillisAdapter())
  .build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

String json = jsonAdapter.toJson(new Post("Introduction to Moshi Json", "Baeldung", Instant.now()));
// {"author":"Baeldung","posted":1582095384793,"title":"Introduction to Moshi Json"}

Post post = jsonAdapter.fromJson(json);
// new Post("Introduction to Moshi Json", "Baeldung", Instant.now())

6. 高度なJSON処理

これで、型をJSONに変換して元に戻すことができ、この変換が行われる方法を制御できます。 ただし、Moshiを使用すると、処理を行う際に必要になる可能性のある、より高度なことがいくつかあります。

6.1. JSONフィールドの名前の変更

場合によっては、JSONにJavaBeanとは異なるフィールド名を付ける必要があります。 これは、Javaでは camelCase 、JSONでは snake_case が必要な場合と同じくらい簡単な場合もあれば、目的のスキーマに一致するようにフィールドの名前を完全に変更する場合もあります。

@Json アノテーションを使用して、制御する任意のBeanの任意のフィールドに新しい名前を付けることができます。

public class Post {
    private String title;
    @Json(name = "authored_by")
    private String author;
    // constructor, getters and setters
}

これを実行すると、MoshiはこのフィールドのJSONでの名前が異なることをすぐに理解します。

Moshi moshi = new Moshi.Builder()
  .build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

Post post = new Post("My Post", "Baeldung");

String json = jsonAdapter.toJson(post);
// {"authored_by":"Baeldung","title":"My Post"}

Post post = jsonAdapter.fromJson(json);
// new Post("My Post", "Baeldung")

6.2. 一時的なフィールド

場合によっては、JSONに含めるべきではないフィールドがある場合があります。 Moshiは、標準のtransient 修飾子を使用して、これらのフィールドがシリアル化または逆シリアル化されないことを示します。

public static class Post {
    private String title;
    private transient String author;
    // constructor, getters and setters
}

次に、シリアル化と逆シリアル化の両方で、このフィールドが完全に無視されることがわかります。

Moshi moshi = new Moshi.Builder()
  .build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

Post post = new Post("My Post", "Baeldung");

String json = jsonAdapter.toJson(post);
// {"title":"My Post"}

Post post = jsonAdapter.fromJson(json);
// new Post("My Post", null)

Post post = jsonAdapter.fromJson("{\"author\":\"Baeldung\",\"title\":\"My Post\"}");
// new Post("My Post", null)

6.3. デフォルト値

JavaBeanのすべてのフィールドの値を含まないJSONを解析している場合があります。 これは問題ありません、そしてモシは正しいことをするために最善を尽くします。

Moshiは、JSONを逆シリアル化するときに引数コンストラクターの形式を使用できませんが、引数なしのコンストラクターが存在する場合は使用できます。

これにより、JSONがシリアル化される前に、Beanに事前入力して、フィールドに必要なデフォルト値を与えることができます。

public class Post {
    private String title;
    private String author;
    private String posted;

    public Post() {
        posted = Instant.now().toString();
    }
    // getters and setters
}

解析されたJSONにtitleまたはauthorフィールドがない場合、これらは値nullになります。 posted フィールドがない場合は、代わりに現在の日付と時刻が表示されます。

Moshi moshi = new Moshi.Builder()
  .build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

String json = "{\"title\":\"My Post\"}";
Post post = jsonAdapter.fromJson(json);
// new Post("My Post", null, "2020-02-19T07:27:01.141Z");

6.4. JSON配列の解析

これまでに行ったことはすべて、単一のJSONオブジェクトを単一のJavabeanにシリアル化および逆シリアル化することを前提としています。 これは非常に一般的なケースですが、それだけではありません。 JSONで配列として表される値のコレクションも操作したい場合があります。

配列がBean内にネストされている場合、何もする必要はありません。 モシはうまくいくでしょう。 JSON全体が配列である場合、Javaジェネリックのいくつかの制限のために、これを実現するためにさらに作業を行う必要があります。 JsonAdapter を、ジェネリックコレクションが逆シリアル化されていること、およびコレクションが何であるかを認識できるように構築する必要があります。

Moshiは、 java.lang.reflect.Type を構築するためのヘルプを提供します。これは、構築時に JsonAdapter に提供できるため、次の追加の一般情報を提供できます。

Moshi moshi = new Moshi.Builder()
  .build();
Type type = Types.newParameterizedType(List.class, String.class);
JsonAdapter<List<String>> jsonAdapter = moshi.adapter(type);

これが完了すると、アダプタは期待どおりに機能し、次の新しい汎用境界を尊重します。

String json = jsonAdapter.toJson(Arrays.asList("One", "Two", "Three"));
// ["One", "Two", "Three"]

List<String> result = jsonAdapter.fromJson(json);
// Arrays.asList("One", "Two", "Three");

7. 概要

MoshiライブラリがJavaクラスのJSONとの変換を非常に簡単にし、柔軟性が高いことを確認しました。 このライブラリは、JavaとJSONの間で変換する必要がある場所であればどこでも使用できます。ファイル、データベース列、さらにはRESTAPIからの読み込みと保存のどちらでもかまいません。 試してみませんか?

いつものように、この記事のソースコードはGitHubにあります。