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 字符串相互转换。
✅ 基本流程:
- 创建
Moshi
实例。 - 通过
moshi.adapter(Class<T>)
获取对应类型的JsonAdapter
。 - 使用
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
注解来标记特定字段。
- 定义注解:
@Retention(RUNTIME)
@Target({FIELD, PARAMETER, METHOD})
@JsonQualifier
public @interface EpochMillis {}
- 编写适配器:
public class EpochMillisAdapter {
@ToJson
public Long toJson(@EpochMillis Instant input) { // 注解在参数上
return input.toEpochMilli();
}
@FromJson
@EpochMillis // 注解在返回值上
public Instant fromJson(Long input) {
return Instant.ofEpochMilli(input);
}
}
- 在字段上使用注解:
public class Post {
private String title;
private String author;
@EpochMillis Instant posted; // 这个字段将使用 EpochMillisAdapter
// 其他字段和方法
}
- 注册并使用:
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。