新一代Json解析库Moshi使用及原理解析

Dependency

implementation ‘com.squareup.moshi:moshi-kotlin:1.8.0’

Reflection
Data类

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。

Codegen

上面提到了Reflection,会导致APK体积增大,所以Moshi还提供了另外一种解析方式,就是注解,Moshi的官方叫法叫做Codegen,因为是采用注解生成的,所以除了添加Moshi的Kotlin依赖之外,还需要加上kapt

kapt ‘com.squareup.moshi:moshi-kotlin-codegen:1.8.0’

改造Data类

给我们的数据类增加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)

JsonAdapter是Moshi有别于Gson,FastJson的最大特点,顾名思义,这是一个Json的转换器,他的主要作用在于将拿到的Json数据转换成任意你想要的类型,Moshi内置了很多JsonAdapter,有如下这些:

Built-in Type Adapters
  • Map:MapJsonAdapter
  • Enums:EnumJsonAdapter
  • Arrays:ArrayJsonAdapter
  • Object:ObjectJsonAdapter
  • String:位于StandardJsonAdapters,采用匿名内部类实现
  • Primitives (int, float, char,boolean) :基本数据类型的Adapter都在StandardJsonAdapters里面,采用匿名内部类实现
Custom Type Adapters

对于一些比较简单规范的数据,使用Moshi内置的JsonAdapter已经完全能够Cover住,但是由于Json只支持基本数据类型传输,所以很多时候不能满足业务上需要,举个例子:

{
“type”: 2,
“isGood”: 1
“title”: “TW9zaGkgaXMgZmxleGlibGU=”
}

这是一个很普通的Json,包含了5个字段,我们如果按照服务端返回的字段来定义解析的Bean,显然是可以完全解析的,但是我们在实际调用的时候,这些数据并不是很干净,我们还需要处理一下:

  • type:Int类型,我需要Enum,我得定义一个Enum的转换类,去将Int转换成Enum
  • isGood:Int类型,我需要Boolean,所以我用的时候还得将Int转成Boolean
  • title:String类型,这个字段是加密过的,可能是通过AES或者RSA加密,这里我们为了方便测试,只是用Base64对_Moshi is flexible_对进行encode。

对于客户端的同学来说,好像没毛病,以前都是这么干的,如果这种_不干净_的Json少点还好,多了之后就很头疼,每个在用的时候都需要转一遍,很多时候我这么干的时候都觉得浪费时间,而今天有了Moshi之后,我们只需要针对需要转换的类型定义对应的JsonAdapter,达到一次定义,一劳永逸的效果,Moshi针对常见的数据类型已经定义了Adapter,但是内置的Adapter现在已经不能满足我们的需求了,所以我们需要自定义JsonAdapter。

实体定义

class ConfigBean {
public CustomType type;
public Boolean isGood;
public String title;
}

此处我们定义的数据类型不是根据服务器返回的Json数据,而是定义的我们业务需要的格式,那么最终是通过JsonAdapter转换器来完成这个转换,下面开始自定义JsonAdapter。

Int->Enum
CustomType

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

定义一个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举个例子,别的基本上都是照葫芦画瓢,没什么难度

StringDecode
TitleAdapter

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

Int->Boolean
BooleanAdapter

public class BooleanAdapter {
@FromJson
public Boolean fromJson(int value) {
return value == 1;
}
@ToJson
public Integer toJson(Boolean value) {
return value ? 1 : 0;
}
}

Adapter测试

下面我们来测试一下

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 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解析重点分析一下。

Builder

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 Builder add(final Type type, final JsonAdapter 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
构造方法

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,点进去看一下

BUILT_IN_FACTORIES

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

JsonAdapter jsonAdapter = moshi.adapter(ConfigBean.class);

不管是我们自定义的JsonAdapter还是Moshi内置的JsonAdapter,最终都是为我们的解析服务的,所以最终所有的JsonAdapter最终汇聚成JsonAdapter,我们看看是怎么生成的,跟一下Moshi的adapter方法,发现最终调用的是下面的方法

public JsonAdapter 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) result;
}

boolean success = false;
JsonAdapter 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 result = (JsonAdapter) factories.get(i).create(type, annotations, this);
if (result == null) continue;
lookupChain.adapterFound(result);
success = true;
return result;
}
}
}

最开始看到这里,我比较奇怪,不太确定我的Config命中了哪一个JsonAdapter,最终通过断点追踪,发现是命中了ClassJsonAdapter,既然命中了他,那么我们就看一下他的具体实现

ClassJsonAdapter

构造方法

final class ClassJsonAdapter extends JsonAdapter {
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 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方法

JsonReader&JsonWriter

对于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更加灵活,并且可以采用注解自动生成。

fromjson

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

toJson

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

Codegen

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生成也就是注解的方式,跟反射对比一下,会发现有如下优点:

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值