Einführung in Moshi Json

1. Einleitung

In diesem Tutorial werfen wir einen Blick auf Moshi, eine moderne JSON-Bibliothek für Java, die uns mit geringem Aufwand eine leistungsstarke JSON-Serialisierung und -Deserialisierung in unserem Code ermöglicht.

Moshi hat eine kleinere API als andere Bibliotheken wie Jackson oder Gson, ohne die Funktionalität zu beeinträchtigen. Dies erleichtert die Integration in unsere Anwendungen und ermöglicht es uns, besser testbaren Code zu schreiben. Es ist auch eine kleinere Abhängigkeit, die für bestimmte Szenarien wichtig sein kann - beispielsweise für die Entwicklung für Android.

2. Hinzufügen von Moshi zu unserem Build

Bevor wir es verwenden können, müssen wir zuerst die Moshi JSON-Abhängigkeiten zu unserer pom.xml- Datei hinzufügen :

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

Die Abhängigkeit com.squareup.moshi: moshi ist die Hauptbibliothek, und die Abhängigkeit com.squareup.moshi: moshi-adapters ist eine Standardadapteradresse, auf die wir später noch näher eingehen werden.

3. Arbeiten mit Moshi und JSON

Mit Moshi können wir alle Java-Werte in JSON konvertieren und überall wieder zurücksetzen, aus welchen Gründen auch immer - z. B. zum Speichern von Dateien, zum Schreiben von REST-APIs oder zu anderen Anforderungen.

Moshi arbeitet mit dem Konzept einer JsonAdapter- Klasse. Dies ist ein typsicherer Mechanismus zum Serialisieren einer bestimmten Klasse in eine JSON-Zeichenfolge und zum Deserialisieren einer JSON-Zeichenfolge in den richtigen Typ:

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

Sobald wir unseren JsonAdapter erstellt haben , können wir ihn jederzeit verwenden, um unsere Werte mithilfe der toJson () -Methode in JSON zu konvertieren :

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

Und natürlich können wir JSON mit der entsprechenden fromJson () -Methode wieder in die erwarteten Java-Typen konvertieren :

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

4. Standard Java Typen

Moshi bietet integrierte Unterstützung für Standard-Java-Typen, die genau wie erwartet zu und von JSON konvertieren. Dies umfasst:

  • Alle Grundelemente - int, float, char usw.
  • Alle Java-Äquivalente - Integer, Float, Character usw.
  • String
  • Aufzählungen
  • Arrays dieser Art
  • Standard-Java-Sammlungen dieser Typen - List, Set, Map

Darüber hinaus arbeitet Moshi automatisch mit einer beliebigen Java-Bean und konvertiert diese in ein JSON-Objekt, in dem die Werte nach denselben Regeln wie bei jedem anderen Typ konvertiert werden. Dies bedeutet natürlich, dass Java-Beans in Java-Beans so tief wie nötig korrekt serialisiert werden.

Die Abhängigkeit von Moshi-Adaptern gibt uns dann Zugriff auf einige zusätzliche Konvertierungsregeln, darunter:

  • Ein etwas leistungsfähigerer Adapter für Enums, der einen Fallback-Wert beim Lesen eines unbekannten Werts aus dem JSON unterstützt
  • Ein Adapter für java.util.Date , der das RFC-3339-Format unterstützt

Die Unterstützung für diese muss bei einer Moshi- Instanz registriert werden, bevor sie verwendet werden können. Wir werden dieses genaue Muster bald sehen, wenn wir Unterstützung für unsere eigenen benutzerdefinierten Typen hinzufügen:

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

5. Benutzerdefinierte Typen in Moshi

Alles bisher hat uns die vollständige Unterstützung für die Serialisierung und Deserialisierung von Java-Objekten in JSON und zurück gegeben. Dies gibt uns jedoch nicht viel Kontrolle darüber, wie der JSON aussieht, indem wir Java-Objekte serialisieren, indem wir buchstäblich jedes Feld im Objekt so schreiben, wie es ist. Das funktioniert, ist aber nicht immer das, was wir wollen.

Stattdessen können wir unsere eigenen Adapter für unsere eigenen Typen schreiben und haben genaue Kontrolle darüber, wie die Serialisierung und Deserialisierung dieser Typen funktioniert.

5.1. Einfache Konvertierungen

Der einfache Fall ist die Konvertierung zwischen einem Java-Typ und einem JSON-Typ - zum Beispiel einer Zeichenfolge. Dies kann sehr nützlich sein, wenn komplexe Daten in einem bestimmten Format dargestellt werden müssen.

Stellen Sie sich zum Beispiel vor, wir haben einen Java-Typ, der den Autor eines Beitrags darstellt:

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

Ohne großen Aufwand wird dies als JSON-Objekt serialisiert, das zwei Felder enthält - Name und E-Mail . Wir möchten es jedoch als einzelne Zeichenfolge serialisieren und den Namen und die E-Mail-Adresse miteinander kombinieren.

Dazu schreiben wir eine Standardklasse, die eine mit @ToJson kommentierte Methode enthält :

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

Natürlich müssen wir auch den anderen Weg gehen. Wir müssen unseren String zurück in unser Author- Objekt analysieren . Dazu fügen Sie stattdessen eine mit @FromJson kommentierte Methode hinzu :

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

Sobald dies erledigt ist, müssen wir dies tatsächlich nutzen. Wir tun dies zu dem Zeitpunkt, an dem wir unser Moshi erstellen, indem wir den Adapter zu unserem Moshi hinzufügen. Builder :

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

Jetzt können wir sofort damit beginnen, diese Objekte in und aus JSON zu konvertieren und die gewünschten Ergebnisse zu erzielen:

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. Komplexe Konvertierungen

Diese Konvertierungen wurden zwischen Java-Beans und primitiven JSON-Typen durchgeführt. Wir können auch in strukturiertes JSON konvertieren, sodass wir einen Java-Typ zum Rendern in unserem JSON in eine andere Struktur konvertieren können.

Beispielsweise müssen wir möglicherweise einen Datums- / Zeitwert als drei verschiedene Werte rendern - das Datum, die Uhrzeit und die Zeitzone.

Mit Moshi müssen wir lediglich einen Java-Typ schreiben, der die gewünschte Ausgabe darstellt, und dann kann unsere @ ToJson- Methode dieses neue Java-Objekt zurückgeben, das Moshi dann unter Verwendung seiner Standardregeln in JSON konvertiert:

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

Wir haben gesehen, wie die Moshi-Bibliothek das Konvertieren von Java-Klassen in und aus JSON wirklich einfach machen kann und wie flexibel es ist. Wir können diese Bibliothek überall dort verwenden, wo wir zwischen Java und JSON konvertieren müssen - egal ob beim Laden und Speichern aus Dateien, Datenbankspalten oder sogar REST-APIs. Warum probieren Sie es nicht aus?

Wie üblich finden Sie den Quellcode für diesen Artikel auf GitHub.