Introduzione a Moshi Json

1. Introduzione

In questo tutorial, daremo uno sguardo a Moshi, una moderna libreria JSON per Java che ci darà una potente serializzazione e deserializzazione JSON nel nostro codice con poco sforzo.

Moshi ha un'API più piccola rispetto ad altre librerie come Jackson o Gson senza compromettere la funzionalità. Ciò semplifica l'integrazione nelle nostre applicazioni e ci consente di scrivere codice più testabile. È anche una dipendenza più piccola, che può essere importante per determinati scenari, come lo sviluppo per Android.

2. Aggiungere Moshi alla nostra build

Prima di poterlo usare, dobbiamo prima aggiungere le dipendenze Moshi JSON al nostro file pom.xml :

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

La dipendenza com.squareup.moshi: moshi è la libreria principale e la dipendenza com.squareup.moshi: moshi-adapters è alcuni adattatori di tipo standard, che esploreremo più in dettaglio in seguito.

3. Lavorare con Moshi e JSON

Moshi ci consente di convertire qualsiasi valore Java in JSON e tornare indietro ovunque sia necessario per qualsiasi motivo, ad esempio per l'archiviazione di file, la scrittura di API REST, qualsiasi esigenza potremmo avere.

Moshi lavora con il concetto di una classe JsonAdapter . Questo è un meccanismo sicuro per i tipi per serializzare una classe specifica in una stringa JSON e deserializzare una stringa JSON nel tipo corretto:

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

Una volta creato il nostro JsonAdapter , possiamo usarlo ogni volta che ne abbiamo bisogno per convertire i nostri valori in JSON utilizzando il metodo 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"}

E, naturalmente, possiamo riconvertire JSON nei tipi Java previsti con il metodo fromJson () corrispondente :

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

4. Tipi Java standard

Moshi viene fornito con il supporto integrato per i tipi Java standard, la conversione da e verso JSON esattamente come previsto. Questo copre:

  • Tutte le primitive: int, float, char , ecc.
  • Tutti gli equivalenti in box Java: Integer, Float, Character , ecc.
  • Corda
  • Enumerazioni
  • Array di questi tipi
  • Raccolte Java standard di questi tipi: elenco, set, mappa

Oltre a questi, Moshi funzionerà automaticamente anche con qualsiasi Java bean arbitrario, convertendolo in un oggetto JSON in cui i valori vengono convertiti utilizzando le stesse regole di qualsiasi altro tipo. Ciò significa ovviamente che i Java bean all'interno di Java bean sono serializzati correttamente fino a quando è necessario.

La dipendenza moshi-adapter ci dà quindi accesso ad alcune regole di conversione aggiuntive, tra cui:

  • Un adattatore leggermente più potente per Enums, che supporta un valore di fallback durante la lettura di un valore sconosciuto da JSON
  • Un adattatore per java.util.Date che supporta il formato RFC-3339

Il supporto per questi deve essere registrato con un'istanza Moshi prima di essere utilizzato. Vedremo presto questo modello esatto quando aggiungeremo il supporto per i nostri tipi personalizzati:

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

5. Tipi personalizzati in Moshi

Finora tutto ci ha fornito supporto totale per serializzare e deserializzare qualsiasi oggetto Java in JSON e viceversa. Ma questo non ci dà molto controllo su come appare il JSON, serializzando gli oggetti Java scrivendo letteralmente ogni campo dell'oggetto così com'è. Funziona ma non è sempre quello che vogliamo.

Invece, possiamo scrivere i nostri adattatori per i nostri tipi e avere il controllo esatto su come funziona la serializzazione e la deserializzazione di questi tipi.

5.1. Conversioni semplici

Il caso semplice è la conversione tra un tipo Java e uno JSON, ad esempio una stringa. Questo può essere molto utile quando è necessario rappresentare dati complessi in un formato specifico.

Ad esempio, immagina di avere un tipo Java che rappresenta l'autore di un post:

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

With no effort at all, this will serialize as a JSON object containing two fields – name and email. We want to serialize it as a single string though, combining the name and email address together.

We do this by writing a standard class that contains a method annotated with @ToJson:

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

Obviously, we need to go the other way as well. We need to parse our string back into our Author object. This is done by adding a method annotated with @FromJson instead:

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

Once done, we need to actually make use of this. We do this at the time we are creating our Moshi by adding the adapter to our Moshi.Builder:

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

Now we can immediately start to convert these objects to and from JSON, and get the results that we wanted:

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. Complex Conversions

These conversions have been between Java beans and JSON primitive types. We can also convert to structured JSON as well – essentially letting us convert a Java type to a different structure for rendering in our JSON.

For example, we might have a need to render a Date/Time value as three different values – the date, the time and the timezone.

Using Moshi, all we need to do is write a Java type representing the desired output and then our @ToJson method can return this new Java object, which Moshi will then convert to JSON using its standard rules:

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

Abbiamo visto come la libreria Moshi possa rendere la conversione di classi Java da e verso JSON davvero facile e quanto sia flessibile. Possiamo utilizzare questa libreria ovunque sia necessario convertire tra Java e JSON, indipendentemente dal fatto che si tratti di caricare e salvare da file, colonne di database o persino API REST. Perché non provarlo?

Come al solito, il codice sorgente di questo articolo può essere trovato su GitHub.