Moshi是Square公司在2015年6月开源的有关Json的反序列化及序列化的框架,说到Json,你们应该很快想到Gson,FastJson以及Jackson等著名的开源框架,那为何还须要Moshi呢?这个主要是因为Kotlin的缘故,咱们知道前面说到的几大解析库主要是针对Java解析Json的,固然他们也支持Kotlin,可是Moshi天生对Kotlin友好,并且对Java的解析也绝不逊色,因此无论是在Java跟Kotlin的混编仍是在纯Kotlin项目中,Moshi表现都很出色。在Java当中,Gson是官方推荐的反序列化及序列化Json框架,一样在Kotlin中,也有官方的库kotlinx.serialization,下面简称KS,这个库在kotlinx中,是单独拿出来了,跟kotlinx.coroutines同样,接下来咱们拿官方库Gson以及Kotlin的官方库KS来作个对比,看看彼此的特色。java
在性能对比以前,咱们先简单对比下这几种解析框架的解析方式git
Method | 支持语言 | 自定义解析 | |
---|---|---|---|
Gson | 反射 | Java/Kotlin | TypeAdapter |
Moshi | 反射/注解 | Java/Kotlin | JsonAdapter |
KS | 编译插件 | Kotlin | KSerializer |
经过上表能够看出,Gson跟Moshi都支持反射解析,KS不支持,并且KS只支持Kotlin,三种解析方式都支持自定义解析器,其中在Kotlin解析时,Moshi支持自动生成JsonAdapter,Gson跟KS须要手动编写,同时的KS能够跨平台,可是KS对于Gradle的版本要求比较高,须要4.7及以上。github
在测试的时候,须要注意几点web
咱们主要比较两点:速度跟稳定性json
这里用豆瓣的API进行测试,Api的地址是api.douban.com/v2/movie/to…,这个是返回豆瓣电影评分排名前250的电影,不过这个API作了限流,每次最多返回100条,因此我强求了2次,而后把2次的Json叠加在一块儿,共200条数据以便于测试,说句题外话,豆瓣在返回的图片格式所有用了webp,确实很优秀。而后咱们就要开始测试了,在测试的时候无论是反序列化仍是序列化,我都只测试了一套Json,而后单个框架测试了10次取平均值,注意是在没有缓存字节码的状况下,也就是首次解析。缘由在于这些开源库的底层实现都是反射,因此他们会缓存字节码,致使第二次解析相同的类,速度都超快,由于只须要赋值,固然你可能会说,一套Json的结果是否是不太靠谱,在本次测试中是很靠谱的,首先是个人Json数据量大,并且嵌套层级多,第二是由于他们底层的实现不一样,在数据量大的时候这个差别会被放大地很明显,一下子看数据你们就知道了。api
Test Code缓存
fun testGsonJava() {
val json = JsonUtils.getJson("douban.json", this)
val deserialstart = System.currentTimeMillis()
val doubanBean = Gson().fromJson(json, DoubanBean::class.java)
val deserizalend = System.currentTimeMillis()
val deserialConsume = deserizalend - deserialstart
val serialstart = System.currentTimeMillis()
val seriJson = Gson().toJson(doubanBean)
val serizalend = System.currentTimeMillis()
val serialConsume = serizalend - serialstart
}
fun testMoshiJava() {
val json = JsonUtils.getJson("douban.json", this)
val jsonAdapter = Moshi.Builder().build().adapter(DoubanBean::class.java)
val deserialstart = System.currentTimeMillis()
val douban = jsonAdapter.fromJson(json)
val deserizalend = System.currentTimeMillis()
val deserialConsume = deserizalend - deserialstart
val serialstart = System.currentTimeMillis()
val seriJson = jsonAdapter.toJson(douban)
val serizalend = System.currentTimeMillis()
val serialConsume = serizalend - serialstart
}
复制代码
Test Result安全
Moshi | Gson | |
---|---|---|
Serialization(ms) | 24/24/23/23/25 | 60/60/59/59/60 |
Deserialization(ms) | 66/65/65/65/67 | 73/79/72/75/74 |
Test Codebash
fun testGsonKotlin() {
val json = JsonUtils.getJson("douban.json", this)
val deserialstart = System.currentTimeMillis()
val doubanBean = Gson().fromJson(json, DoubanBean::class.javaObjectType)
val deserizalend = System.currentTimeMillis()
val deserialConsume = deserizalend - deserialstart
val serialstart = System.currentTimeMillis()
val seriJson = Gson().toJson(doubanBean)
val serizalend = System.currentTimeMillis()
val serialConsume = serizalend - serialstart
}
fun testMoshiKotlin() {
val json = JsonUtils.getJson("douban.json", this)
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter = moshi.adapter(DoubanBean::class.java)
val deserialstart = System.currentTimeMillis()
val douban = jsonAdapter.fromJson(json)
val deserizalend = System.currentTimeMillis()
val deserialConsume = deserizalend - deserialstart
val serialstart = System.currentTimeMillis()
val seriJson = jsonAdapter.toJson(douban)
val serizalend = System.currentTimeMillis()
val serialConsume = serizalend - serialstart
}
fun testKotlinXSerialize() {
val json = JsonUtils.getJson("douban.json", this)
val start = System.currentTimeMillis()
val douban = JSON.parse(DoubanBean.serializer(), json)
val end = System.currentTimeMillis()
val consume = end - start
val serialstart = System.currentTimeMillis()
val seriJson = JSON.stringify(DoubanBean.serializer(), douban)
val serizalend = System.currentTimeMillis()
val serialConsume = serizalend - serialstart
}
复制代码
Test Result服务器
Moshi | Gson | KS | |
---|---|---|---|
Serialization(ms) | 23/27/23/24/27 | 91/85/85/86/86 | 38/37/36/43/37 |
Deserialization(ms) | 74/74/73/74/73 | 93/93/94/89/92 | 73/72/73/77/71 |
因为Moshi底层的IO操做采用的是Okio,因此在序列化的时候性能优于Gson及KS以及其它框架,这个是很好理解的,在反序列化的过程当中,咱们看到Moshi的解析效率跟Kotlin的官方序列化工具基本持平,可是稍快于Gson,本次测试中没有把Moshi建立Adapter的时间计算在内,由于他是能够单首创建做为一个单例,跟解析保持相互独立,跟前面提到的最优解保持一致。
稳定性主要包含两个方面:默认值和空安全
咱们知道,在Java的解析过程当中,若是在Json中缺乏某个字段,咱们的Bean对象原有的值保持不变,可是因为Gson没法识别Kotlin的构造函数,致使默认值会失效,下面举个例子:
@Serializable
data class Chinese(@Optional val age: Int = 0, @Optional val country: String? = "China") {
@Optional
private val hobby: String = "travel"
}
fun main(args: Array<String>) {
val gsonBean = Gson().fromJson("""{"age":4}""", Chinese::class.javaObjectType)
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val adapter = moshi.adapter(Chinese::class.java)
val moshiBean = adapter.fromJson("""{"age":4}""")
val kxBean = JSON.parse(Chinese.serializer(), """{"age":4}""")
}
复制代码
咱们解析上述数据,发现Gson解析到的gsonBean对象中的country及hobby这两个字段都是null,可是Moshi跟KX反序列化后的对象country跟hobby都是咱们给予的默认值,这个问题Gson在解析Java的时候是没有的,可是在Kotlin中就失效了。缘由能够从Gson的源码中获得答案,在采用反射解析的时候,Gson构造对象实例时调用的是默认无参构造方法,因此没有默认值也就不足为奇了。那么hobby为何也没有,由于在Gson并不知道什么是数据类,因此他依然不认识hobby。
在Java中,咱们能够用注解@Nullable和NotNull来标记一个变量或者方法参数是否可空,可是加注解比较麻烦,因此咱们不少时候都不会去加注解,通常都是在使用的时候进行非空判断,因此Java代码在调用解析后的Bean对象的时候都须要进行非空判断,Kotlin在这种状况下进行了完善,能够在定义的时候指定对象是否可空,这样在使用非空对象的时候就无需进行判断了,可是若是针对一个方法的参数是非空的,你传入了一个空值,编译就会报错,那么一样的道理,若是咱们在定义Data类的时候,若是指定了一个字段为非空类型,那么若是Json数据里面这个字段为Null就应该报错,下面看看三个框架的实现逻辑
@Serializable
data class Chinese(@Optional val age: Int = 0, @Optional val country: String? = "China") {
@Optional
private val hobby: String = "travel"
}
fun main(args: Array<String>) {
val gsonBean = Gson().fromJson("""{"age":null}""", Chinese::class.javaObjectType)
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val adapter = moshi.adapter(Chinese::class.java)
val moshiBean = adapter.fromJson("""{"age":null}""")
val kxBean = JSON.parse(Chinese.serializer(), """{"age":null}""")
}
复制代码
测试的时候发现Moshi跟KS都报错了,可是Gson是正常的,按照Kotlin的语法这个是不合理的,咱们是须要报错的,由于age字段是不可空的,而这里却传了一个空参数,因此Gson在这里的处理是有问题的。缘由咱们以前说过,虽然Kotlin最终被编译成的字节码也是运行在JVM上的,可是Gson反射的时候没法区分Java跟Kotlin,因此仍是按照Java的解析规则去解析的,由于Json的key为Null在Java中是正常的,即便这在Kotlin中已经没法执行。
针对上面的测试,下面根据项目的实际使用状况总结一下
混编项目:使用Moshi,兼顾Java跟Kotlin
Java项目:建议使用Gson,若是反序列化需求比较多,建议使用Moshi,由于它内置Okio
Kotlin项目:跨平台的话,使用KS;非跨平台,若是仅仅是反序列化,Moshi跟KS都可,若是序列化较多,使用Moshi
implementation 'com.squareup.moshi:moshi:1.8.0'
复制代码
String json = ...;
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Bean> jsonAdapter = moshi.adapter(Bean.class);
//Deserialize
Bean bean = jsonAdapter.fromJson(json);
//Serialize
String json = jsonAdapter.toJson(bean);
复制代码
Moshi moshi = new Moshi.Builder().build();
Type listOfCardsType = Types.newParameterizedType(List.class, Bean.class);
JsonAdapter<List<Bean>> jsonAdapter = moshi.adapter(listOfCardsType);
//Deserialize
List<Bean> beans = jsonAdapter.fromJson(json);
//Serialize
String json = jsonAdapter.fromJson(json);
复制代码
Moshi moshi = new Moshi.Builder().build();
ParameterizedType newMapType = Types.newParameterizedType(Map.class, String.class, Integer.class);
JsonAdapter<Map<String,Integer>> jsonAdapter = moshi.adapter(newMapType);
//Deserialize
Map<String,Integer> beans = jsonAdapter.fromJson(json);
//Serialize
String json = jsonAdapter.fromJson(json);
复制代码
public final class Bean {
@Json(name = "lucky number") int luckyNumber;
@Json(name = "objec") int data;
@Json(name = "toatl_price") String totolPrice;
private transient int total;//jump the field
}
复制代码
相对于Java只能经过反射进行解析,针对Kotlin,Moshi提供了两种解析方式,一种是经过Reflection,一种是经过Codegen本质上是经过注解处理器,你能够采用其中的一种,也能够两种都使用,下面分别介绍下这两种解析方式
implementation 'com.squareup.moshi:moshi-kotlin:1.8.0'
复制代码
data class ConfigBean(
var isGood: Boolean = false,
var title: String = "",
var type: CustomType = CustomType.DEFAULT
)
复制代码
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
复制代码
这种方式会引入Kotlin-Reflect的Jar包,大概有2.5M。
上面提到了Reflection,会致使APK体积增大,因此Moshi还提供了另一种解析方式,就是注解,Moshi的官方叫法叫作Codegen,由于是采用注解生成的,因此除了添加Moshi的Kotlin依赖以外,还须要加上kapt
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.8.0'
复制代码
给咱们的数据类增长JsonClass注解
@JsonClass(generateAdapter = true)
data class ConfigBean(
var isGood: Boolean = false,
var title: String = "",
var type: CustomType = CustomType.DEFAULT
)
复制代码
这样的话,Moshi会在编译期生成咱们须要的JsonAdapter,而后经过JsonReader遍历的方式去解析Json数据,这种方式不只仅不依赖于反射,并且速度快于Kotlin。
这种经过注解生成的Adpter,不须要进行注册,Moshi会经过注解自动帮咱们注册到Factory里面,这里就不贴代码了,你们能够去看下官方文档,Read the Fucking Source Code。
JsonAdapter是Moshi有别于Gson,FastJson的最大特色,顾名思义,这是一个Json的转换器,他的主要做用在于将拿到的Json数据转换成任意你想要的类型,Moshi内置了不少JsonAdapter,有以下这些:
对于一些比较简单规范的数据,使用Moshi内置的JsonAdapter已经彻底可以Cover住,可是因为Json只支持基本数据类型传输,因此不少时候不能知足业务上须要,举个例子:
{
"type": 2,
"isGood": 1
"title": "TW9zaGkgaXMgZmxleGlibGU="
}
复制代码
这是一个很普通的Json,包含了5个字段,咱们若是按照服务端返回的字段来定义解析的Bean,显然是能够彻底解析的,可是咱们在实际调用的时候,这些数据并非很干净,咱们还须要处理一下:
对于客户端的同窗来讲,好像没毛病,之前都是这么干的,若是这种不干净的Json少点还好,多了以后就很头疼,每一个在用的时候都须要转一遍,不少时候我这么干的时候都以为浪费时间,而今天有了Moshi以后,咱们只须要针对须要转换的类型定义对应的JsonAdapter,达到一次定义,一劳永逸的效果,Moshi针对常见的数据类型已经定义了Adapter,可是内置的Adapter如今已经不能知足咱们的需求了,因此咱们须要自定义JsonAdapter。
class ConfigBean {
public CustomType type;
public Boolean isGood;
public String title;
}
复制代码
此处咱们定义的数据类型不是根据服务器返回的Json数据,而是定义的咱们业务须要的格式,那么最终是经过JsonAdapter转换器来完成这个转换,下面开始自定义JsonAdapter。
enum CustomType {
DEFAULT(0, "DEFAULT"), BAD(1, "BAD"), NORMAL(2, "NORMAL"), GOOD(3, "NORMAL");
public int type;
public String content;
CustomType(int type, String content) {
this.type = type;
this.content = content;
}
}
复制代码
定义一个TypeAdapter继承自JsonAdapter,传入对应的泛型,会自动帮咱们复写fromJson跟toJson两个方法
public class TypeAdapter {
@FromJson
public CustomType fromJson(int value) throws IOException {
CustomType type = CustomType.DEFAULT;
switch (value) {
case 1:
type = CustomType.BAD;
break;
case 2:
type = CustomType.NORMAL;
break;
case 3:
type = CustomType.GOOD;
break;
}
return type;
}
@ToJson
public Integer toJson(CustomType value) {
return value != null ? value.type : 0;
}
}
复制代码
至此已经完成Type的转换,接下来咱们再以title举个例子,别的基本上都是照葫芦画瓢,没什么难度
public class TitleAdapter {
@FromJson
public String fromJson(String value) {
byte[] decode = Base64.getDecoder().decode(value);
return new String(decode);
}
@ToJson
public String toJson(String value) {
return new String(Base64.getEncoder().encode(value.getBytes()));
}
}
复制代码
public class BooleanAdapter {
@FromJson
public Boolean fromJson(int value) {
return value == 1;
}
@ToJson
public Integer toJson(Boolean value) {
return value ? 1 : 0;
}
}
复制代码
下面咱们来测试一下
String json = "{\n" + "\"type\": 2,\n" + "\"isGood\": 1,\n"
+ "\"title\": \"TW9zaGkgaXMgZmxleGlibGU=\"\n"+ "}";
Moshi moshi = new Moshi.Builder()
.add(new TypeAdapter())
.add(new TitleAdapter())
.add(new BooleanAdapter())
.build();
JsonAdapter<ConfigBean> jsonAdapter = moshi.adapter(ConfigBean.class);
ConfigBean cofig = jsonAdapter.fromJson(json);
System.out.println("=========Deserialize ========");
System.out.println(cofig);
String cofigJson = jsonAdapter.toJson(cofig);
System.out.println("=========serialize ========");
System.out.println(cofigJson);
复制代码
打印Log
=========Deserialize ========
ConfigBean{type=CustomType{type=2, content='NORMAL'}, isGood=true, title='Moshi is flexible'}
=========serialize ========
{"isGood":1,"title":"TW9zaGkgaXMgZmxleGlibGU=","type":2}
复制代码
符合咱们预期的结果,而且咱们在开发的时候,只须要将Moshi设置成单例的,一次性将全部的Adapter所有add进去,就能够一劳永逸,而后愉快地进行开发了。
Moshi底层采用了Okio进行优化,可是上层的JsonReader,JsonWriter等代码是直接从Gson借鉴过来的,因此再也不过多分析,主要是就Moshi的两大特性JsonAdapter以及Kotlin的Codegen解析重点分析一下。
Moshi moshi = new Moshi.Builder().add(new BooleanAdapter()).build();
复制代码
Moshi是经过Builder模式进行构建的,支持添加多个JsonAdapter,下面先看看Builder源码
public static final class Builder {
//存储全部Adapter的建立方式,若是没有添加自定义Adapter,则为空
final List<JsonAdapter.Factory> factories = new ArrayList<>();
//添加自定义Adapter,并返回自身
public Builder add(Object adapter) {
return add(AdapterMethodsFactory.get(adapter));
}
//添加JsonAdapter的建立方法到factories里,并返回自身
public Builder add(JsonAdapter.Factory factory) {
factories.add(factory);
return this;
}
//添加JsonAdapter的建立方法集合到factories里,并返回自身
public Builder addAll(List<JsonAdapter.Factory> factories) {
this.factories.addAll(factories);
return this;
}
//经过Type添加Adapter的建立方法,并返回自身
public <T> Builder add(final Type type, final JsonAdapter<T> jsonAdapter) {
return add(new JsonAdapter.Factory() {
@Override
public @Nullable JsonAdapter<?> create(
Type targetType, Set<? extends Annotation> annotations, Moshi moshi) { return annotations.isEmpty() && Util.typesMatch(type, targetType) ? jsonAdapter : null;
}
});
}
//建立一个Moshi的实例
public Moshi build() {
return new Moshi(this);
}
}
复制代码
经过源码发现Builder保存了全部自定义Adapter的建立方式,而后调用Builder的build方式建立了一个Moshi的实例,下面看一下Moshi的源码。
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);
}
复制代码
构造方法里面建立了factories,而后加入了Builder中的factories,而后又增长了一个BUILT_IN_FACTORIES,咱们应该也能猜到这个就是Moshi内置的JsonAdapter,点进去看一下
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);
}
复制代码
BUILT_IN_FACTORIES这里面提早用一个静态代码块加入了全部内置的JsonAdapter
JsonAdapter<ConfigBean> jsonAdapter = moshi.adapter(ConfigBean.class);
复制代码
无论是咱们自定义的JsonAdapter仍是Moshi内置的JsonAdapter,最终都是为咱们的解析服务的,因此最终全部的JsonAdapter最终汇聚成JsonAdapter,咱们看看是怎么生成的,跟一下Moshi的adapter方法,发现最终调用的是下面的方法
public <T> JsonAdapter<T> adapter(Type type, Set<? extends Annotation> annotations, @Nullable String fieldName) {
type = canonicalize(type);
// 若是有对应的缓存,那么直接返回缓存
Object cacheKey = cacheKey(type, annotations);
synchronized (adapterCache) {
JsonAdapter<?> result = adapterCache.get(cacheKey);
if (result != null) return (JsonAdapter<T>) result;
}
boolean success = false;
JsonAdapter<T> adapterFromCall = lookupChain.push(type, fieldName, cacheKey);
try {
if (adapterFromCall != null)
return adapterFromCall;
// 遍历Factories,直到命中泛型T的Adapter
for (int i = 0, size = factories.size(); i < size; i++) {
JsonAdapter<T> result = (JsonAdapter<T>) factories.get(i).create(type, annotations, this);
if (result == null) continue;
lookupChain.adapterFound(result);
success = true;
return result;
}
}
}
复制代码
最开始看到这里,我比较奇怪,不太肯定个人Config命中了哪个JsonAdapter,最终经过断点追踪,发现是命中了ClassJsonAdapter,既然命中了他,那么咱们就看一下他的具体实现
构造方法
final class ClassJsonAdapter<T> extends JsonAdapter<T> {
public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() {
@Override public @Nullable JsonAdapter<?> create(
Type type, Set<? extends Annotation> annotations, Moshi moshi) {
//省略了不少异常判断代码
Class<?> rawType = Types.getRawType(type);
//获取Class的全部类型
ClassFactory<Object> classFactory = ClassFactory.get(rawType);
Map<String, FieldBinding<?>> fields = new TreeMap<>();
for (Type t = type; t != Object.class; t = Types.getGenericSuperclass(t)) {
//建立Moshi跟Filed的绑定关系,便于解析后赋值
createFieldBindings(moshi, t, fields);
}
return new ClassJsonAdapter<>(classFactory, fields).nullSafe();
}
}
复制代码
当咱们拿到一个JsonAdapter的时候,基本上全部的构建都已经完成,此时能够进行Deserialize 或者Serialize 操做,先看下Deserialize 也就是fromjson方法
对于Java的解析,Moshi并无在传输效率上进行显著的提高,只是底层的IO操做采用的是Okio,Moshi的创新在于灵活性上面,也就是JsonAdapter,并且Moshi的官方文档上面也提到了
Moshi uses the same streaming and binding mechanisms as Gson. If you’re a Gson user you’ll find Moshi works similarly. If you try Moshi and don’t love it, you can even migrate to Gson without much violence!
因此这里的JsonReader跟JsonWriter说白了都是从Gson那里直接拷过来的,就是这么坦诚,不过Moshi也不是所有都是拿来主义,站在Gson 的肩膀上,Moshi的JsonAdapter更加灵活,而且能够采用注解自动生成。
ConfigBean cofig = jsonAdapter.fromJson(json);
复制代码
这个方法先是调用了父类JsonAdapter的fromJson方法
public abstract T fromJson(JsonReader reader) throws IOException;
public final T fromJson(BufferedSource source) throws IOException {
return fromJson(JsonReader.of(source));
}
public final T fromJson(String string) throws IOException {
JsonReader reader = JsonReader.of(new Buffer().writeUtf8(string));
T result = fromJson(reader);
return result;
复制代码
咱们发现fromJson是个重载方法,既能够传String也能够传BufferedSource,不过最终调用的都是fromJson(JsonReader reader)这个方法,BufferedSource是Okio的一个类,由于Moshi底层的IO采用的是Okio,可是咱们发现参数为JsonReader的这个方法是抽象方法,因此具体的实现是是在ClassJsonAdapter里面,。
@Override public T fromJson(JsonReader reader) throws IOException {
T result = classFactory.newInstance();
try {
reader.beginObject();
while (reader.hasNext()) {
int index = reader.selectName(options);
//若是不是Key,直接跳过
if (index == -1) {
reader.skipName();
reader.skipValue();
continue;
}
//解析赋值
fieldsArray[index].read(reader, result);
}
reader.endObject();
return result;
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
//递归调用,直到最后
void read(JsonReader reader, Object value) throws IOException, IllegalAccessException {
T fieldValue = adapter.fromJson(reader);
field.set(value, fieldValue);
}
复制代码
String cofigJson = jsonAdapter.toJson(cofig);
复制代码
跟fromJson同样,先是调用的JsonAdapter的toJson方法
public abstract void toJson(JsonWriter writer, T value) throws IOException;
public final void toJson(BufferedSink sink, T value) throws IOException {
JsonWriter writer = JsonWriter.of(sink);
toJson(writer, value);
}
public final String toJson( T value) {
Buffer buffer = new Buffer();
try {
toJson(buffer, value);
} catch (IOException e) {
throw new AssertionError(e); // No I/O writing to a Buffer.
}
return buffer.readUtf8();
}
复制代码
无论传入的是泛型T仍是BufferedSink,最终调用的toJson(JsonWriter writer),而后返回了buffer.readUtf8()。咱们继续看一会儿类的具体实现
@Override public void toJson(JsonWriter writer, T value) throws IOException {
try {
writer.beginObject();
for (FieldBinding<?> fieldBinding : fieldsArray) {
writer.name(fieldBinding.name);
//将fieldsArray的值依次写入writer里面
fieldBinding.write(writer, value);
}
writer.endObject();
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
复制代码
Moshi’s Kotlin codegen support is an annotation processor. It generates a small and fast adapter for each of your Kotlin classes at compile time. Enable it by annotating each class that you want to encode as JSON:
所谓Codegen,也就是咱们上文提到的Annotation,在编译期间生成对应的JsonAdapter,咱们看一下先加一下注解,看看Kotlin帮咱们自动生成的注解跟咱们自定义的注解有什么区别,rebuild一下项目:
CustomType
@JsonClass(generateAdapter = true)
data class CustomType(var type: Int, var content: String)
复制代码
咱们来看一下对应生成的JsonAdapter
CustomTypeJsonAdapter
这个类方法不少,咱们重点看一下formJson跟toJson
override fun fromJson(reader: JsonReader): CustomType {
private val options: JsonReader.Options = JsonReader.Options.of("type", "content", "age")
var type: Int? = null
var content: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.selectName(options)) {
//按照变量的定义顺序依次赋值
0 -> type = intAdapter.fromJson(reader)
1 -> content = stringAdapter.fromJson(reader)
-1 -> {
reader.skipName()
reader.skipValue()
}
}
}
reader.endObject()
//不经过反射,直接建立对象,传入解析的Value
var result = CustomType(type = type ,content = content )
return result
}
override fun toJson(writer: JsonWriter, value: CustomType?) {
writer.beginObject()
writer.name("type")//写入type
intAdapter.toJson(writer, value.type)
writer.name("content")//写入content
stringAdapter.toJson(writer, value.content)
writer.endObject()
}
复制代码
在看这段代码以前,我开始很奇怪Moshi为何在遍历JsonReader的时候要经过Int类型的变量来判断,而不是经过JsonReader的Name来解析,由于通常拿到一个JsonReader以后,咱们都是下面这种写法:
override fun fromJson(reader: JsonReader): CustomType {
var type: Int? = null
var content: String? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
//按照变量的定义顺序依次赋值
"type" -> type = reader.nextInt()
"content" -> content = reader.nextString()
else -> {
reader.skipValue()
}
}
}
reader.endObject()
//不经过反射,直接建立对象,传入解析的Value
var result = CustomType(type = type ,content = content )
return result
}
//省略toJson
复制代码
相比于咱们本身写的代码,Moshi生成的注解中的代码是把Json的key提取出来了,放到一个Options里面去了,在放的同时也天然生成了一个index,可能这里不太好理解,为何要转成int呢,这样的话效率反而不是更低了么,由于刚开始建立对象的时候须要转一次,读取key的时候也要转一次,这样还不如直接用String来的快,下面咱们跟一下源码,看看selectName里面的具体实现
/** * If the next token is a {@linkplain Token#NAME property name} that's in {@code options}, this * consumes it and returns its index. Otherwise this returns -1 and no name is consumed. */
@CheckReturnValue
public abstract int selectName(Options options) throws IOException;
复制代码
经过注释咱们能够看到selectName的注释,咱们传入一个Options,返回一个索引,这个索引也就是咱们以前放进去的key的索引,这样会提升解析效率么,直观看起来好像是画蛇添足,直接把这个Key的名字给我就行了么,为何还要换成0跟1,可读性反而贬低了。若是你的key只重复一次,那么转不转成index都是同样的,由于从二进制流到string须要一个decode,若是咱们解析的是一个列表,那么同一个key会被decode屡次,decode须要时间也须要空间,因此当咱们解析无重复的key的时候,换成index跟不换是同样的,效率差很少,可是当咱们解析List的时候,换成Index的时候对于相同的Key咱们只须要decode一次,这个在解析列表的时候效率会大大提高。
ConfigBean
@JsonClass(generateAdapter = true)
data class ConfigBean(var isGood: Boolean ,var title: String ,var type: CustomType) 复制代码
ConfigBeanJsonAdapter
override fun fromJson(reader: JsonReader): ConfigBean {
var isGood: Boolean? = null
var title: String? = null
var type: CustomType? = null
reader.beginObject()
while (reader.hasNext()) {
when (reader.selectName(options)) {
0 -> isGood = booleanAdapter.fromJson(reader)
1 -> title = stringAdapter.fromJson(reader)
2 -> type = customTypeAdapter.fromJson(reader)
-1 -> {
reader.skipName()
reader.skipValue()
}
}
}
reader.endObject()
var result = ConfigBean(isGood = isGood ,title = title ,type = type
return result
}
override fun toJson(writer: JsonWriter, value: ConfigBean?) {
writer.beginObject()
writer.name("isGood")
booleanAdapter.toJson(writer, value.isGood)
writer.name("title")
stringAdapter.toJson(writer, value.title)
writer.name("type")
customTypeAdapter.toJson(writer, value.type)
writer.endObject()
}
复制代码
经过查看生成的CustomTypeJsonAdapter以及ConfigBeanJsonAdapter,咱们发现经过Codegen生成也就是注解的方式,跟反射对比一下,会发现有以下优势:
在进行kotlin解析的时候无论是采用Reflect仍是Codegen,都必须保证类型一致,也就是父类跟子类必须是Java或者kotlin,由于两种解析方式,最终都是经过ClassType来进行解析的,同时在使用Codegen解析的时候必须保证Koltin的类型是internal
或者public
的。
Moshi整个用法跟源码看下来,其实并非很复杂,可是针对Java跟Kotlin的解析增长了灵活的JsonAdapter,而且在Kotlin中能够自动生成,虽然Gson跟KS也都支持自定义解析,可是赋值须要手动编写,开发效率较低。不过Moshi也有些缺点,对于Kotlin的Null类型的支持并不友好,这样会在Kotlin解析的时候若是对于一个不可空的字段变成了Null就会直接抛异常,感受不太友好,应该给个默认值或者直接置空比较好一些,还有就是对默认值的支持,若是Json出现了Null类型,那么解析到对应的字段依然会被赋值成Null,跟以前的Gson同样,不过从最新官方的commit已经有人提了issue跟MR,来给非空类型的字段遇到Json数据对应的Key为Null的时候给予一个默认值,应该会在1.9.0中进行更新,你们能够关注一下。