1. 概述

本文将带你快速上手 Moshi,这是一个现代化的 Java JSON 库,能够在几乎不增加开发成本的前提下,为你的应用提供强大且类型安全的序列化与反序列化能力。

相较于 Jackson 或 Gson,Moshi 的 API 更加简洁,功能却不打折扣。这使得它更容易集成,也更利于编写可测试的代码。同时,它的依赖体积更小,对于 Android 开发这类对包大小敏感的场景尤其友好。

2. 添加 Moshi 依赖

在使用 Moshi 之前,需要先将其依赖添加到 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>
  • com.squareup.moshi:moshi 是核心库。
  • com.squareup.moshi:moshi-adapters 提供了一些常用类型的标准适配器(adapters),比如 Date、增强版 Enum 等,后续会用到。

3. Moshi 基础用法

Moshi 的核心是 **JsonAdapter**,它是一个类型安全的工具,用于将特定的 Java 类与 JSON 字符串相互转换。

基本流程:

  1. 创建 Moshi 实例。
  2. 通过 moshi.adapter(Class<T>) 获取对应类型的 JsonAdapter
  3. 使用 toJson()fromJson() 进行转换。

来看一个简单的例子:

public class Post {
    private String title;
    private String author;
    private String text;
    // 构造函数、getter 和 setter 省略
}

Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

有了 JsonAdapter,就可以轻松完成序列化和反序列化:

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

反向操作同样简单:

Post post = jsonAdapter.fromJson(json);
// 成功还原为 Post 对象

4. 内置支持的 Java 类型

Moshi 开箱即用,支持绝大多数标准 Java 类型,转换行为符合直觉:

  • ✅ 所有基本类型(int, float, char 等)
  • ✅ 对应的包装类型(Integer, Float, Character 等)
  • String
  • ✅ 枚举(enum
  • ✅ 上述类型的数组
  • ✅ 标准集合类(List, Set, Map 等)

对于任意的 Java Bean,Moshi 也能自动将其序列化为 JSON 对象,其内部字段会递归地使用上述规则进行转换,支持深度嵌套。

⚠️ 注意: moshi-adapters 模块提供的额外支持(如 java.util.Date)需要手动注册Moshi 实例中才能生效:

Moshi moshi = new Moshi.Builder()
  .add(new Rfc3339DateJsonAdapter()) // 支持 RFC-3339 格式的 Date
  .add(CurrencyCode.class, EnumJsonAdapter.create(CurrencyCode.class).withUnknownFallback(CurrencyCode.USD)) // Enum 未知值时的默认 fallback
  .build();

5. 自定义类型适配器

虽然 Moshi 能自动处理 Java Bean,但默认行为是“字段直出”,无法满足所有场景。比如,你可能希望将一个复杂对象序列化为单个字符串,或者改变其 JSON 结构。这时就需要自定义 JsonAdapter

5.1 简单转换(Java 对象 ↔ JSON 原始类型)

目标:将 Author 对象序列化为 "name <email>" 格式的字符串。

public class Author {
    private String name;
    private String email;
    // 构造函数、getter 和 setter 省略
}

创建适配器类,使用 @ToJson@FromJson 注解:

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

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

注册适配器并使用:

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

效果:

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

5.2 复杂转换(Java 对象 ↔ JSON 结构)

目标:将 ZonedDateTime 序列化为包含 date, time, timezone 三个字段的 JSON 对象。

首先定义目标结构:

public class JsonDateTime {
    private String date;
    private String time;
    private String timezone;
    // 构造函数、getter 和 setter 省略
}

编写适配器:

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

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

使用:

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

5.3 条件化适配器(@JsonQualifier

有时,你希望同一个类型不同字段上有不同的序列化方式。例如,一个 Instant 字段需要输出为 ISO 字符串,另一个则需要输出为时间戳(毫秒)。

这时,可以使用 @JsonQualifier 注解来标记特定字段。

  1. 定义注解:
@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
@JsonQualifier
public @interface EpochMillis {}
  1. 编写适配器:
public class EpochMillisAdapter {
    @ToJson
    public Long toJson(@EpochMillis Instant input) { // 注解在参数上
        return input.toEpochMilli();
    }
    
    @FromJson
    @EpochMillis // 注解在返回值上
    public Instant fromJson(Long input) {
        return Instant.ofEpochMilli(input);
    }
}
  1. 在字段上使用注解:
public class Post {
    private String title;
    private String author;
    @EpochMillis Instant posted; // 这个字段将使用 EpochMillisAdapter
    // 其他字段和方法
}
  1. 注册并使用:
Moshi moshi = new Moshi.Builder()
  .add(new EpochMillisAdapter())
  .build();
JsonAdapter<Post> jsonAdapter = moshi.adapter(Post.class);

String json = jsonAdapter.toJson(new Post("Intro", "Baeldung", Instant.now()));
// {"author":"Baeldung","posted":1582095384793,"title":"Intro"}

6. 高级 JSON 处理技巧

6.1 重命名 JSON 字段

当 Java 字段名与 JSON 字段名不一致时(如 Java 用 camelCase,JSON 用 snake_case),使用 @Json 注解:

public class Post {
    private String title;
    @Json(name = "authored_by") // JSON 中的字段名为 authored_by
    private String author;
    // ...
}

效果:

// 序列化输出: {"authored_by":"Baeldung","title":"My Post"}

6.2 忽略字段(Transient)

使用 transient 关键字标记不需要序列化的字段:

public static class Post {
    private String title;
    private transient String author; // 此字段将被忽略
    // ...
}

效果:

// 序列化输出: {"title":"My Post"}
// 反序列化时,author 字段为 null

6.3 默认值

Moshi 在反序列化时,如果 JSON 缺少某个字段,该字段的值将为 null(引用类型)或默认值(基本类型)。若想为字段设置非空的默认值,可以在类的无参构造函数中初始化:

public class Post {
    private String title;
    private String author;
    private String posted;

    public Post() {
        this.posted = Instant.now().toString(); // 设置默认值
    }
    // ...
}

当 JSON 不包含 posted 字段时,它会自动填充当前时间。

6.4 解析 JSON 数组

当整个 JSON 是一个数组时,由于 Java 泛型擦除,直接传 List.class 无法获取泛型信息。必须使用 Types.newParameterizedType() 构造带泛型的 Type

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

使用:

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

List<String> result = jsonAdapter.fromJson(json);
// 成功解析为 List<String>

7. 总结

Moshi 以其简洁的 API 和强大的扩展性,成为处理 Java 与 JSON 转换的优秀选择。无论是简单的 Bean 映射,还是复杂的自定义序列化逻辑,它都能提供优雅的解决方案。其小巧的体积也使其成为 Android 项目的理想之选。

你可以在文件存储、数据库交互或 REST 接口通信中尝试使用 Moshi,体验其带来的便捷。文中的完整示例代码可在 GitHub 上找到:https://github.com/eugenp/tutorials/tree/master/json-modules/json-2


原始标题:Introduction to Moshi Json