Pengenalan Moshi Json

1. Pengenalan

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

Moshi mempunyai API yang lebih kecil daripada perpustakaan lain seperti Jackson atau Gson tanpa menjejaskan fungsi. Ini menjadikannya lebih mudah untuk disatukan ke dalam aplikasi kami dan membolehkan kami menulis lebih banyak kod yang dapat diuji. Ini juga merupakan ketergantungan yang lebih kecil, yang mungkin penting untuk senario tertentu - seperti mengembangkan untuk Android.

2. Menambah Moshi ke Bangunan Kami

Sebelum dapat menggunakannya, pertama-tama kita perlu menambahkan kebergantungan Moshi JSON ke fail pom.xml kami :

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

The com.squareup.moshi: moshi pergantungan adalah perpustakaan utama, dan com.squareup.moshi: moshi-penyesuai pergantungan adalah beberapa jenis penyesuai standard - yang kita akan meneroka dengan lebih terperinci kemudian.

3. Bekerja dengan Moshi dan JSON

Moshi membolehkan kita mengubah nilai Java menjadi JSON dan kembali lagi di mana sahaja kita perlu dengan alasan apa pun - contohnya untuk penyimpanan fail, menulis API REST, apa sahaja keperluan yang mungkin kita ada.

Moshi berfungsi dengan konsep kelas JsonAdapter . Ini adalah mekanisme tipe untuk menyusun kelas tertentu menjadi rentetan JSON dan untuk mendeserialisasikan rentetan JSON kembali ke jenis yang betul:

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 membina JsonAdapter kami , kami dapat menggunakannya kapan pun kami perlu untuk menukar nilai kami ke JSON menggunakan kaedah 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 menukar JSON kembali ke jenis Java yang diharapkan dengan kaedah dariJson () yang sesuai :

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

4. Jenis Java Standard

Moshi hadir dengan sokongan terpasang untuk jenis Java standard, menukar ke dan dari JSON tepat seperti yang diharapkan. Ini merangkumi:

  • Semua primitif - int, float, char , dll.
  • Semua setara kotak Java - Integer, Float, Character , dll.
  • Tali
  • Jumlah
  • Susunan jenis ini
  • Koleksi Java standard jenis ini - Daftar, Set, Peta

Sebagai tambahan kepada ini, Moshi juga akan secara otomatis bekerja dengan kacang Jawa yang sewenang-wenang, mengubahnya menjadi objek JSON di mana nilainya ditukar menggunakan peraturan yang sama dengan jenis lain. Ini jelas bermaksud bahawa kacang jawa di dalam biji jawa disusun dengan betul sedalam yang perlu kita pergi.

The moshi-penyesuai pergantungan kemudian memberikan kami akses kepada beberapa peraturan penukaran tambahan, termasuk:

  • Penyesuai yang lebih kuat untuk Enums - menyokong nilai penggantian ketika membaca nilai yang tidak diketahui dari JSON
  • Penyesuai untuk java.util.Date yang menyokong format RFC-3339

Sokongan untuk ini perlu didaftarkan dengan instance Moshi sebelum dapat digunakan. Kami akan melihat corak tepat ini tidak lama lagi apabila kami menambahkan sokongan untuk jenis tersuai kami sendiri:

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

5. Jenis Adat di Moshi

Segala-galanya setakat ini telah memberi kami sokongan penuh untuk membuat siri dan deserialisasi objek Java ke JSON dan belakang. Tetapi ini tidak memberi kita banyak kendali atas bagaimana JSON, menyusun objek Java dengan menulis setiap bidang dalam objek sebagaimana adanya. Ini berfungsi tetapi tidak selalu seperti yang kita mahukan.

Sebagai gantinya, kita boleh menulis penyesuai kita sendiri untuk jenis kita sendiri dan mempunyai kawalan yang tepat mengenai bagaimana siri dan deserialisasi jenis ini berfungsi.

5.1. Penukaran Mudah

Kes sederhana adalah menukar antara jenis Java dan JSON - contohnya rentetan. Ini sangat berguna apabila kita perlu mewakili data kompleks dalam format tertentu.

Sebagai contoh, bayangkan kita mempunyai jenis Java yang mewakili pengarang entri:

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

Tanpa usaha sama sekali, ini akan bersiri sebagai objek JSON yang mengandungi dua bidang - nama dan e - mel . Kami mahu menjadikannya sebagai rentetan tunggal, menggabungkan nama dan alamat e-mel bersama-sama.

Kami melakukannya dengan menulis kelas standard yang mengandungi kaedah yang diberi penjelasan dengan @ToJson :

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

Jelas, kita juga harus pergi ke arah yang lain. Kita perlu menguraikan rentetan kita kembali ke objek Pengarang kita . Ini dilakukan dengan menambahkan kaedah yang diberi penjelasan 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 perlu menggunakan ini. Kami melakukan ini pada masa kami membuat Moshi kami dengan menambahkan penyesuai ke Moshi kami. Pembina :

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

Sekarang kita boleh mula menukar objek ini ke dan dari JSON, dan mendapatkan hasil yang kita mahukan:

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

Penukaran ini antara kacang Java dan jenis primitif JSON. Kita juga dapat menukar ke JSON terstruktur juga - pada dasarnya membiarkan kita menukar jenis Java ke struktur yang berbeza untuk rendering di JSON kita.

Sebagai contoh, kita mungkin perlu memberikan nilai Tarikh / Waktu sebagai tiga nilai yang berbeza - tarikh, waktu dan zon waktu.

Dengan menggunakan Moshi, yang perlu kita lakukan adalah menulis jenis Java yang mewakili output yang diinginkan dan kemudian kaedah @ToJson kami dapat mengembalikan objek Java baru ini, yang kemudian Moshi akan ubah menjadi JSON menggunakan peraturan standardnya:

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 menjadikan kelas Java penukaran ke dan dari JSON sangat mudah, dan betapa fleksibelnya. Kita boleh menggunakan perpustakaan ini di mana sahaja yang kita perlukan untuk menukar antara Java dan JSON - sama ada memuat dan menyimpan dari fail, lajur pangkalan data atau bahkan REST API. Mengapa tidak mencubanya?

Seperti biasa, kod sumber untuk artikel ini boleh didapati di GitHub.