内容简介:相对于Java只能通过反射进行解析,针对Kotlin,Moshi提供了两种解析方式,一种是通过这种方式会引入Kotlin-Reflect的Jar包,大概有2.5M。
Moshi 是Square公司在2015年6月开源的一个Json解析库,相对于Gson,FastJson等老牌解析库而言,Moshi不仅支持对Kotlin的解析,并且提供了Reflection跟Annotion两种解析Kotlin的方法,除此之外,Moshi最大的改变在于支持自定义JsonAdapter,能够将Json的Value转换成任意你需要的类型。
基本用法之Java
Dependency
implementation 'com.squareup.moshi:moshi:1.7.0' 复制代码
Bean
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); 复制代码
List
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); 复制代码
Map
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); 复制代码
Others
- @json:Key转换
- transitent:跳过该字段不解析
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 } 复制代码
基本用法之Kotlin
相对于 Java 只能通过反射进行解析,针对Kotlin,Moshi提供了两种解析方式,一种是通过 Reflection ,一种是通过 Annotation ,你可以采用其中的一种,也可以两种都使用,下面分别介绍下这两种解析方式
Dependency
implementation 'com.squareup.moshi:moshi-kotlin:1.7.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。
Annotation
上面提到了Reflection,会导致APK体积增大,所以Moshi还提供了另外一种解析方式,就是注解,Moshi的官方叫法叫做Codegen,因为是采用注解生成的,所以除了添加Moshi的Kotlin依赖之外,还需要加上kapt
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.7.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。
高级用法(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<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 解析重点分析一下。
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 <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
构造方法
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<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 ,既然命中了他,那么我们就看一下他的具体实现
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方法
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那里直接借鉴过来的,就是这么坦诚。
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 { 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() } 复制代码
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生成也就是注解的方式,跟反射对比一下,会发现有如下优点:
- 效率高:直接创建对象,无需反射
- APK体积小:无需引入Kotlin-reflect的Jar包
注意事项
在进行kotlin解析的时候不管是采用Reflect还是Codegen,都必须保证类型一致,也就是父类跟子类必须是Java或者kotlin,因为两种解析方式,最终都是通过ClassType来进行解析的,同时在使用Codegen解析的时候必须保证Koltin的类型是 internal
或者 public
的。
总结
Moshi整个用法跟源码看下来,其实并不是很复杂,但是针对Java跟Kotlin的解析增加了JsonAdapter的转换,以及针对Kotlin的Data类的解析提供了Codegen这种方式,真的让人耳目一新,以前遇到这种业务调用的时候需要二次转换的时候,都是去写 工具 类或者用的时候直接转换。不过Moshi也有些缺点,对于Kotlin的Null类型的支持并不友好,这样会在Kotlin解析的时候如果对于一个不可空的字段变成了Null就会直接抛异常,感觉不太友好,应该给个默认值比较好一些,还有就是对默认值的支持,如果Json出现了Null类型,那么解析到对应的字段依然会被赋值成Null,跟之前的Gson一样,希望以后可以进行完善,毕竟瑕不掩瑜。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。