最近 FastJson 安全问题频发,所以 JSON 解析又被拉到台面上,而正好不久前看 Reddit 看到 Gson 的作者在推荐一个叫 Moshi 的库,这就花点时间看一下。1
Gson 存在的问题
序列化 Date 的时候不包含时区的信息
Date epoch = new Date(0);
String epochJson = new Gson().toJson(epoch);
// "Dec 31, 1969 7:00:00 PM"
RFC 3339 标准 里面规定的日期表示法:
2020-06-12T07:20:50.52Z
其中 T
用来分割前面的日期和后面的时间,而最后的 Z
表示这个时间是 UTC+0
,其他人看到这个时间就可以根据自己的时区进行转换。2
Gson 在序列化 HTML 标签时,会进行 HTML escaping 成:
private String e = "12 & 5";
private String f = "12 > 6"
"e":"12 \u0026 5"
"f":"12 \u003e 6"
Moshi 优势
- Moshi 库特别小,对于 Android 来说自然可以减小 APK 大小
- Moshi 自带的 Adapter 可以满足大部分的需求,如果需要扩充也非常方便
- 可以使用
@Json
来自定义 Field 名 - Kotlin Support
- 使用类似于
@HexColor int
的限定符可以让多个 JSON 映射到同一个 Java Type
Moshi 潜在的问题和 Gson 的差别
- Moshi 只有少量的内置 Adapter,Moshi 不会去序列化平台相关的类型,比如
java.*
,javax.*
,android.*
等等,以防止被 Lock 在某一个特殊 JDK 或者 Android 版本 - Moshi 只有少量的配置选项,没有 field naming strapegy, versioning, instance creator, long serialization policy.
- Moshi 没有
JsonElement
模型 - No HTML-safe escaping
使用
更加具体的使用方法可以参考源代码中的实现。
最基本的使用,序列化一个 Java Object 到 JSON,或者将 JSON 映射到 Java Object 里面。
将 JSON 字符串解析成 Java 对象
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
BlackjackHand blackjackHand = jsonAdapter.fromJson(json);
System.out.println(blackjackHand);
如果是 JsonArray:
Moshi moshi = new Moshi.Builder().build();
Type listOfCardsType = Types.newParameterizedType(List.class, Card.class);
JsonAdapter<List<Card>> jsonAdapter = moshi.adapter(listOfCardsType);
List<Card> cards = jsonAdapter.fromJson(json);
序列化 Java Object:
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<BlackjackHand> jsonAdapter = moshi.adapter(BlackjackHand.class);
String json = jsonAdapter.toJson(blackjackHand);
System.out.println(json);
自定义 Field
比如有一个 JSON 字符串:
String json = ""
+ "{"
+ " \"username\": \"jesse\","
+ " \"lucky number\": 32"
+ "}\n";
其中的 lucky number
是带空格的,假如要解析到 Java Object
public final class Player {
public final String username;
public final @Json(name = "lucky number") int luckyNumber;
}
可以看到使用 @Json
注解即可。
源码解析
Moshi
Moshi 只能使用 Builder 模式创建,看其源码会发现,构造函数并不是 public 的。唯一一个带参数的
Moshi(Builder builder) {
List<JsonAdapter.Factory> factories = new ArrayList<>(
builder.factories.size() + BUILT_IN_FACTORIES.size());
factories.addAll(builder.factories);
factories.addAll(BUILT_IN_FACTORIES);
this.factories = Collections.unmodifiableList(factories);
}
可以看到 Moshi 有一系列 adapter() 公开方法,通过 adapter() 方法可以返回一个 JsonAdapter<>
对象,之后的操作都在该 adapter
之上进行。
成员变量
private final List<JsonAdapter.Factory> factories;
private final ThreadLocal<LookupChain> lookupChainThreadLocal = new ThreadLocal<>();
private final Map<Object, JsonAdapter<?>> adapterCache = new LinkedHashMap<>();
说明:
factories
: 是 JsonAdapter.Factory 数组,工厂模式产生 JsonAdapter- lookupChainThreadLocal 是一个 ThreadLocal 内部存放了
LookupChain
adapterCache
是一个LinkedHashMap
用来保存 Object 到 JsonAdapter 的映射关系
Moshi 类中初始化的时候有 5 个内置的 Adapter Factory.
static final List<JsonAdapter.Factory> BUILT_IN_FACTORIES = new ArrayList<>(5);
static {
BUILT_IN_FACTORIES.add(StandardJsonAdapters.FACTORY);
BUILT_IN_FACTORIES.add(CollectionJsonAdapter.FACTORY);
BUILT_IN_FACTORIES.add(MapJsonAdapter.FACTORY);
BUILT_IN_FACTORIES.add(ArrayJsonAdapter.FACTORY);
BUILT_IN_FACTORIES.add(ClassJsonAdapter.FACTORY);
}
- StandardJsonAdapters.FACTORY 包含了基本类型,包装类型,还有运行时才能决定的 ObjectJsonAdapter
- CollectionJsonAdapter.FACTORY 包含 List, Collection, Set 类型
- MapJsonAdapter.FACTORY 包含将 Map(Key 是 String) 的 JSON 转换成 Object 的 Adapter
- ArrayJsonAdapter.FACTORY 处理包含了原始值或 Object 的 JSON Array
- ClassJsonAdapter.FACTORY
Moshi.Builder
Moshi.Builder 是 Moshi 内部的一个类,用来创建 Moshi,它有一系列方法:
com.squareup.moshi.Moshi.Builder#add(java.lang.reflect.Type, com.squareup.moshi.JsonAdapter<T>)
com.squareup.moshi.Moshi.Builder#add(java.lang.reflect.Type, java.lang.Class<? extends java.lang.annotation.Annotation>, com.squareup.moshi.JsonAdapter<T>)
com.squareup.moshi.Moshi.Builder#add(com.squareup.moshi.JsonAdapter.Factory)
com.squareup.moshi.Moshi.Builder#add(java.lang.Object)
com.squareup.moshi.Moshi.Builder#addAll
com.squareup.moshi.Moshi.Builder#build
可以看到除了 overload 重载的 add()
方法外,就是 build
方法,而 add()
方法可以添加一系列类型。
JsonAdapter 抽象类
JsonAdapter 的公开方法:
com.squareup.moshi.JsonAdapter#fromJson(com.squareup.moshi.JsonReader)
com.squareup.moshi.JsonAdapter#fromJson(okio.BufferedSource)
com.squareup.moshi.JsonAdapter#fromJson(java.lang.String)
com.squareup.moshi.JsonAdapter#toJson(com.squareup.moshi.JsonWriter, T)
com.squareup.moshi.JsonAdapter#toJson(okio.BufferedSink, T)
com.squareup.moshi.JsonAdapter#toJson(T)
com.squareup.moshi.JsonAdapter#toJsonValue
com.squareup.moshi.JsonAdapter#fromJsonValue
com.squareup.moshi.JsonAdapter#serializeNulls
com.squareup.moshi.JsonAdapter#nullSafe
com.squareup.moshi.JsonAdapter#nonNull
com.squareup.moshi.JsonAdapter#lenient
com.squareup.moshi.JsonAdapter#failOnUnknown
com.squareup.moshi.JsonAdapter#indent
JsonAdapter 有两个抽象方法需要实现 fromJson
和 toJson
。
大致就能看出 JsonAdapter 的主要功能:
- 一方面提供
fromJson
totoJson
的转换 -
另一方面提供转换过程中的一些选项
serializeNulls
serializes nulls when encoding JSONnullSafe
support for reading and writing- 比如
nonNull
调用时会返回一个不允许 null 值的 JsonAdapter lenient
宽容处理 JSONfailOnUnknown
在遇到未知的 name 或 value 时抛出JsonDataException
异常indent
输出的 JSON 是格式化好的
JsonAdapter 主要去处理各个类型的转换,需要实现如下三个方法:
static final JsonAdapter<Boolean> BOOLEAN_JSON_ADAPTER = new JsonAdapter<Boolean>() {
@Override public Boolean fromJson(JsonReader reader) throws IOException {
return reader.nextBoolean();
}
@Override public void toJson(JsonWriter writer, Boolean value) throws IOException {
writer.value(value.booleanValue());
}
@Override public String toString() {
return "JsonAdapter(Boolean)";
}
};
JsonAdapter.Factory 接口
这个接口定义在抽象类 JsonAdapter 中,Factory 只需要实现一个方法:
public interface Factory {
@CheckReturnValue
@Nullable JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi);
}
Factory 看名字也能猜到这是一个工厂方法,用来创造给定 Type 给定 Annotation 类型的 Adapter,如果无法创建则返回 null.
在 Factory 的实现中,可能会使用 moshi.adapter 来构建 Adapter.
Mosh.Builder
再看 Moshi.Builder 类成员
final List<JsonAdapter.Factory> factories = new ArrayList<>();
在源代码的基础上加了一些注释