Introduction to Moshi Json – Moshi Json简介

最后修改: 2020年 2月 29日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll take a look at Moshi, a modern JSON library for Java that will give us powerful JSON serialization and deserialization in our code with little effort.

在本教程中,我们将看看Moshi,这是一个适用于Java的现代JSON库,它将在我们的代码中以很小的代价获得强大的JSON序列化和反序列化。

Moshi has a smaller API than other libraries like Jackson or Gson without compromising on functionality. This makes it easier to integrate into our applications and lets us write more testable code. It is also a smaller dependency, which may be important for certain scenarios – such as developing for Android.

与Jackson或Gson等其他库相比,Moshi的API较小,但在功能上没有妥协。这使得它更容易集成到我们的应用程序中,并让我们编写更多可测试的代码。它也是一个较小的依赖性,这对某些场景可能很重要–比如为Android开发。

2. Adding Moshi to Our Build

2.将Moshi加入我们的建设

Before we can use it, we first need to add the Moshi JSON dependencies to our pom.xml file:

在使用它之前,我们首先需要将Moshi JSON依赖项添加到我们的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>

The com.squareup.moshi:moshi dependency is the main library, and the com.squareup.moshi:moshi-adapters dependency is some standard type adapters – which we’ll explore in more detail later.

com.squareup.moshi:moshi依赖性是主库,com.squareup.moshi:moshi-adapters依赖性是一些标准类型适配器–我们将在后面详细探讨。

3. Working with Moshi and JSON

3.使用Moshi和JSON工作

Moshi allows us to convert any Java values into JSON and back again anywhere we need to for whatever reasons – e.g. for file storage, writing REST APIs, whatever needs we might have.

Moshi允许我们将任何Java值转换为JSON,并在我们需要的任何地方再次转换,无论出于什么原因–例如用于文件存储、编写REST APIs,以及我们可能有的任何需求。

Moshi works with the concept of a JsonAdapter class. This is a typesafe mechanism to serialize a specific class into a JSON string and to deserialize a JSON string back into the correct type:

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

Once we’ve built our JsonAdapter, we can use it whenever we need to in order to convert our values to JSON using the toJson() method:

一旦我们建立了我们的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"}

And, of course, we can convert JSON back into the expected Java types with the corresponding fromJson() method:

当然,我们也可以用相应的fromJson()方法将JSON转换回预期的Java类型。

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

4. Standard Java Types

4.标准的Java类型

Moshi comes with built-in support for standard Java types, converting to and from JSON exactly as expected. This covers:

Moshi内置了对标准Java类型的支持,完全按照预期的方式转换为JSON。这包括

In addition to these, Moshi will also automatically work with any arbitrary Java bean, converting this to a JSON object where the values are converted using the same rules as any other type. This obviously means that Java beans within Java beans are correctly serialized as deep as we need to go.

除了这些,Moshi还将自动处理任何任意的Java Bean,将其转换为JSON对象,其中的值使用与其他类型相同的规则进行转换。这显然意味着Java Bean中的Java Bean被正确地序列化,因为我们需要深入了解。

The moshi-adapters dependency then gives us access to some additional conversion rules, including:

然后,moshi-adapters依赖关系使我们能够访问一些额外的转换规则,包括。

  • A slightly more powerful adapter for Enums – supporting a fallback value when reading an unknown value from the JSON
  • An adapter for java.util.Date supporting the RFC-3339 format

Support for these needs to be registered with a Moshi instance before they’ll be used. We’ll see this exact pattern soon when we add support for our own custom types:

对这些的支持需要在使用前在Moshi实例中注册。当我们为自己的自定义类型添加支持时,我们将很快看到这种确切的模式。

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

5. Custom Types in Moshi

5.Moshi中的自定义类型

Everything so far has given us total support for serializing and deserializing any Java object into JSON and back. But this doesn’t give us much control over what the JSON looks like, serializing Java objects by literally writing every field in the object as-is. This works but is not always what we want.

到目前为止,所有的东西都支持将任何Java对象序列化和反序列化为JSON并返回。但这并没有让我们对JSON的外观有太多的控制,序列化Java对象的方式是将对象中的每个字段按原样写入。这很有效,但并不总是我们想要的。

Instead, we can write our own adapters for our own types and have exact control over how the serialization and deserialization of these types works.

相反,我们可以为我们自己的类型编写自己的适配器,并对这些类型的序列化和反序列化的工作方式进行精确控制。

5.1. Simple Conversions

5.1.简单的转换

The simple case is converting between a Java type and a JSON one – for example a string. This can be very useful when we need to represent complex data in a specific format.

简单的情况是在Java类型和JSON类型之间进行转换–比如说字符串。当我们需要用特定的格式来表示复杂的数据时,这可能是非常有用的。

For example, imagine we have a Java type representing the author of a post:

例如,设想我们有一个Java类型,代表一个帖子的作者。

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.

不费吹灰之力,这将序列化为一个包含两个字段的JSON对象 – nameemail。但我们想把它序列化为一个单一的字符串,把名字和电子邮件地址结合在一起。

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

我们通过编写一个标准的类,其中包含一个用@ToJson注解的方法来做到这一点。

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

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:

很明显,我们也需要走另一条路。我们需要将字符串解析回我们的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;
}

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.Builder中来实现这一点。

Moshi moshi = new Moshi.Builder()
  .add(new AuthorAdapter())
  .build();
JsonAdapter<Post> 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:

现在我们可以立即开始将这些对象转换为JSON,并得到我们想要的结果。

Post post = new Post("My Post", new Author("Baeldung", "baeldung@example.com"), "This is my post");
String json = jsonAdapter.toJson(post);
// {"author":"Baeldung <baeldung@example.com>","text":"This is my post","title":"My Post"}

Post post = jsonAdapter.fromJson(json);
// new Post("My Post", new Author("Baeldung", "baeldung@example.com"), "This is my post");

5.2. Complex Conversions

5.2.复杂的转换

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.

这些转换都是在Java Bean和JSON原始类型之间进行的。我们也可以转换为结构化的JSON – 基本上让我们把一个Java类型转换为不同的结构,以便在我们的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:

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

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方法来完成另一种方式,该方法接收我们新的JSON结构化类型并返回我们想要的类型。

@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:

然后,我们能够完全按照上面的方法,将我们的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. Alternative Type Adapters

5.3.替代类型的适配器

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.

例如,我们可能有一种情况,我们需要将日期和时间呈现为从纪元开始的毫秒,而不是ISO-8601字符串。

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:

Moshi让我们通过使用一个特别注释来做到这一点,然后我们可以将其应用于我们的字段和我们的适配器。

@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.

这其中的关键部分是@JsonQualifier注解,它允许Moshi将任何用这个注解的字段与适当的Adapter方法联系起来。

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:

接下来,我们需要写一个适配器。像往常一样,我们有一个@FromJson和一个@ToJson方法来转换我们的类型和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.

这里,我们在@ToJson方法的输入参数和@FromJson方法的返回值上使用了我们的注解。

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

Moshi现在可以使用这个适配器或任何同样用@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:

我们现在能够将我们的注释类型转换为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. Advanced JSON Processing

6.高级JSON处理

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.

现在,我们可以将我们的类型转换为JSON,然后再转换回来,而且我们可以控制这种转换的方式。虽然有一些更高级的东西,我们可能需要在处理过程中做,但Moshi让我们很容易实现这些。

6.1. Renaming JSON Fields

6.1.重命名JSON字段

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.

有时,我们需要我们的JSON与我们的Java Bean有不同的字段名。这可能很简单,在Java中需要camelCase,在JSON中需要snake_case,也可能是要完全重命名字段以匹配所需的模式。

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

我们可以使用@Json注解来给我们控制的任何Bean中的任何字段起一个新名字。

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马上就能理解这个字段在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. Transient Fields

6.2.瞬态场

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:

在某些情况下,我们可能有一些字段不应该被包含在JSON中。Moshi使用标准的transient限定词来表示这些字段不被序列化或反序列化。

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<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. Default Values

6.3.默认值

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.

有时我们解析的JSON并不包含我们Java Bean中每个字段的值。这很好,Moshi会尽力去做正确的事情。

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.

在反序列化我们的JSON时,Moshi不能使用任何形式的参数构造器,但如果有一个无参数构造器,它能够使用。

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

这将允许我们在JSON被序列化之前预先填充我们的Bean,给我们的字段提供任何所需的默认值。

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:

如果我们解析的JSON缺少titleauthor字段,那么这些字段最终将以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. Parsing JSON Arrays

6.4.解析JSON数组

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.

到目前为止,我们所做的一切都假定我们正在将一个单一的JSON对象序列化和反序列化为一个单一的Java Bean。这是一个非常常见的情况,但它不是唯一的情况。有时我们也想处理值的集合,这些值在我们的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.

当数组被嵌套在我们的Bean里面时,就没有什么可做的了。Moshi将只是工作。当整个JSON是一个数组时,我们必须做更多的工作来实现这一点,只是因为Java泛型的一些限制。我们需要构建我们的JsonAdapter,让它知道它正在反序列化一个通用集合,以及这个集合是什么。

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提供了一些帮助,以构建一个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);

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<String> result = jsonAdapter.fromJson(json);
// Arrays.asList("One", "Two", "Three");

7. Summary

7.总结

We’ve seen how the Moshi library can make converting Java classes to and from JSON really easy, and how flexible it is. We can use this library anywhere that we need to convert between Java and JSON – whether that’s loading and saving from files, database columns or even REST APIs. Why not try it out?

我们已经看到Moshi库如何使Java类与JSON的转换变得非常容易,以及它的灵活性。我们可以在任何需要在Java和JSON之间进行转换的地方使用这个库–无论是从文件、数据库列,甚至是REST APIs中加载和保存。为什么不试试呢?

As usual, the source code for this article can be found over on GitHub.

像往常一样,本文的源代码可以在GitHub上找到超过