Pengantar Moshi Json

1. Perkenalan

Dalam tutorial ini, kita akan melihat Moshi, perpustakaan JSON modern untuk Java yang akan memberi kita serialisasi dan deserialisasi JSON yang kuat dalam kode kita dengan sedikit usaha.

Moshi memiliki API yang lebih kecil daripada pustaka lain seperti Jackson atau Gson tanpa mengorbankan fungsionalitas. Ini membuatnya lebih mudah untuk diintegrasikan ke dalam aplikasi kami dan memungkinkan kami menulis kode yang lebih dapat diuji. Ini juga merupakan ketergantungan yang lebih kecil, yang mungkin penting untuk skenario tertentu - seperti mengembangkan untuk Android.

2. Menambahkan Moshi ke Build Kami

Sebelum kita bisa menggunakannya, pertama kita perlu menambahkan dependensi Moshi JSON ke file pom.xml kita :

 com.squareup.moshi moshi 1.9.2   com.squareup.moshi moshi-adapters 1.9.2 

The com.squareup.moshi: moshi ketergantungan adalah perpustakaan utama, dan com.squareup.moshi: moshi-adapter ketergantungan adalah beberapa jenis adapter standar - yang kita akan mengeksplorasi secara lebih rinci nanti.

3. Bekerja dengan Moshi dan JSON

Moshi memungkinkan kita untuk mengubah nilai Java apa pun menjadi JSON dan kembali lagi di mana pun kita perlu untuk alasan apa pun - misalnya untuk penyimpanan file, menulis REST API, apa pun kebutuhan yang mungkin kita miliki.

Moshi bekerja dengan konsep kelas JsonAdapter . Ini adalah mekanisme aman jenis untuk membuat serial kelas tertentu ke dalam string JSON dan untuk men-deserialisasi string JSON kembali ke jenis yang benar:

public class Post { private String title; private String author; private String text; // constructor, getters and setters } Moshi moshi = new Moshi.Builder().build(); JsonAdapter jsonAdapter = moshi.adapter(Post.class);

Setelah kita membangun JsonAdapter kita, kita bisa menggunakannya kapan pun kita perlu untuk mengubah nilai kita menjadi JSON menggunakan metode toJson () :

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"}

Dan, tentu saja, kita dapat mengonversi JSON kembali menjadi tipe Java yang diharapkan dengan metode fromJson () yang sesuai:

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

4. Jenis Java Standar

Moshi hadir dengan dukungan built-in untuk tipe Java standar, mengonversi ke dan dari JSON persis seperti yang diharapkan. Ini meliputi:

  • Semua primitif - int, float, char , dll.
  • Semua kotak Java yang setara - Integer, Float, Character , dll.
  • Tali
  • Enum
  • Array jenis ini
  • Koleksi Java Standar dari jenis ini - List, Set, Map

Selain itu, Moshi juga akan secara otomatis bekerja dengan kacang Java sembarang, mengubahnya menjadi objek JSON di mana nilai diubah menggunakan aturan yang sama seperti jenis lainnya. Ini jelas berarti bahwa biji Java dalam biji Java diserialkan dengan benar sedalam yang kita butuhkan.

The moshi-adapter ketergantungan kemudian memberi kita akses ke beberapa aturan konversi tambahan, termasuk:

  • Adaptor yang sedikit lebih kuat untuk Enums - mendukung nilai fallback saat membaca nilai yang tidak diketahui dari JSON
  • Adaptor untuk java.util.Date yang mendukung format RFC-3339

Dukungan untuk ini perlu didaftarkan dengan instans Moshi sebelum dapat digunakan. Kami akan segera melihat pola yang tepat ini saat kami menambahkan dukungan untuk jenis kustom kami sendiri:

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

5. Jenis Kustom di Moshi

Semuanya sejauh ini telah memberi kami dukungan total untuk membuat serial dan deserialisasi objek Java apa pun ke JSON dan kembali. Tapi ini tidak memberi kita banyak kendali atas seperti apa JSON itu, membuat serial objek Java dengan menulis setiap bidang di objek apa adanya. Ini berhasil tetapi tidak selalu yang kita inginkan.

Sebagai gantinya, kami dapat menulis adaptor kami sendiri untuk jenis kami sendiri dan memiliki kontrol yang tepat atas cara kerja serialisasi dan deserialisasi jenis ini.

5.1. Konversi Sederhana

Kasus sederhananya adalah mengubah antara tipe Java dan JSON - misalnya string. Ini bisa sangat berguna ketika kita perlu merepresentasikan data kompleks dalam format tertentu.

Misalnya, bayangkan kita memiliki tipe Java yang mewakili penulis posting:

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

Tanpa usaha sama sekali, ini akan menjadi serial sebagai objek JSON yang berisi dua bidang - nama dan email . Kami ingin membuat serial sebagai string tunggal, menggabungkan nama dan alamat email bersama.

Kami melakukan ini dengan menulis kelas standar yang berisi metode yang dianotasi dengan @ToJson :

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

Jelas, kita juga harus pergi ke arah lain. Kita perlu mengurai string kita kembali ke objek Penulis kita . Ini dilakukan dengan menambahkan metode yang dianotasi dengan @FromJson sebagai gantinya:

@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; }

Setelah selesai, kita harus benar-benar memanfaatkan ini. Kami melakukan ini pada saat kami membuat Moshi kami dengan menambahkan adaptor ke Moshi.Builder kami :

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

Sekarang kita dapat segera mulai mengubah objek ini ke dan dari JSON, dan mendapatkan hasil yang kita inginkan:

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. Konversi Kompleks

Konversi ini terjadi antara kacang Java dan tipe primitif JSON. Kami juga dapat mengonversi ke JSON terstruktur - pada dasarnya memungkinkan kami mengonversi tipe Java ke struktur berbeda untuk rendering di JSON kami.

Misalnya, kita mungkin perlu merender nilai Tanggal / Waktu sebagai tiga nilai berbeda - tanggal, waktu, dan zona waktu.

Menggunakan Moshi, yang perlu kita lakukan hanyalah menulis tipe Java yang mewakili keluaran yang diinginkan dan kemudian metode @ToJson kita dapat mengembalikan objek Java baru ini, yang kemudian akan diubah Moshi ke JSON menggunakan aturan standarnya:

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); } }

As we can expect, going the other way is done by writing an @FromJson method that takes our new JSON structured type and returns our desired one:

@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); }

We are then able to use this exactly as above to convert our ZonedDateTime into our structured output and back:

Moshi moshi = new Moshi.Builder() .add(new JsonDateTimeAdapter()) .build(); JsonAdapter 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. Alternative Type Adapters

Sometimes we want to use an alternative adapter for a single field, as opposed to basing it on the type of the field.

For example, we might have a single case where we need to render date and time as milliseconds from the epoch instead of as an ISO-8601 string.

Moshi lets us do this by the use of a specially-annotated annotation which we can then apply both to our field and our adapter:

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

The key part of this is the @JsonQualifier annotation, which allows Moshi to tie any fields annotated with this to the appropriate Adapter methods.

Next, we need to write an adapter. As always we have both a @FromJson and a @ToJson method to convert between our type and JSON:

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

Here, we've used our annotation on the input parameter to the @ToJson method and on the return value of the @FromJson method.

Moshi can now use this adapter or any field that is also annotated with @EpochMillis:

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

We are now able to convert our annotated type to JSON and back as needed:

Moshi moshi = new Moshi.Builder() .add(new EpochMillisAdapter()) .build(); JsonAdapter 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. Advanced JSON Processing

Now that we can convert our types to JSON and back, and we can control the way that this conversion happens. There are some more advanced things that we may need to do on occasion with our processing though, which Moshi makes easy to achieve.

6.1. Renaming JSON Fields

On occasion, we need our JSON to have different field names to our Java beans. This may be as simple as wanting camelCase in Java and snake_case in JSON, or it might be to completely rename the field to match the desired schema.

We can use the @Json annotation to give a new name to any field in any bean that we control:

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

Once we've done this, Moshi immediately understands that this field has a different name in the JSON:

Moshi moshi = new Moshi.Builder() .build(); JsonAdapter 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. Transient Fields

In certain cases, we may have fields that should not be included in the JSON. Moshi uses the standard transient qualifier to indicate that these fields are not to be serialized or deserialized:

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

We will then see that this field is completely ignored both when serializing and deserializing:

Moshi moshi = new Moshi.Builder() .build(); JsonAdapter 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. Default Values

Sometimes we are parsing JSON that does not contain values for every field in our Java Bean. This is fine, and Moshi will do its best to do the right thing.

Moshi is not able to use any form of argument constructor when deserializing our JSON, but it is able to use a no-args constructor if one is present.

This will then allow us to pre-populate our bean before the JSON is serialized, giving any required default values to our fields:

public class Post { private String title; private String author; private String posted; public Post() { posted = Instant.now().toString(); } // getters and setters }

If our parsed JSON is lacking the title or author fields then these will end up with the value null. If we are lacking the posted field then this will instead have the current date and time:

Moshi moshi = new Moshi.Builder() .build(); JsonAdapter 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. Parsing JSON Arrays

Everything that we've done so far has assumed that we are serializing and deserializing a single JSON object into a single Java bean. This is a very common case, but it's not the only case. Sometimes we want to also work with collections of values, which are represented as an array in our JSON.

When the array is nested inside of our beans, there's nothing to do. Moshi will just work. When the entire JSON is an array then we have to do more work to achieve this, simply because of some limitations in Java generics. We need to construct our JsonAdapter in a way that it knows it is deserializing a generic collection, as well as what the collection is.

Moshi offers some help to construct a java.lang.reflect.Type that we can provide to the JsonAdapter when we build it so that we can provide this additional generic information:

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

Once this is done, our adapter works exactly as expected, honoring these new generic bounds:

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

7. Summary

Kami telah melihat bagaimana perpustakaan Moshi dapat membuat konversi kelas Java ke dan dari JSON sangat mudah, dan betapa fleksibelnya itu. Kita dapat menggunakan pustaka ini di mana saja yang kita butuhkan untuk mengonversi antara Java dan JSON - baik itu memuat dan menyimpan dari file, kolom database, atau bahkan REST API. Mengapa tidak mencobanya?

Seperti biasa, kode sumber untuk artikel ini dapat ditemukan di GitHub.