新一代Json解析库Moshi源码解析

栏目: 后端 · 前端 · 发布时间: 7年前

内容简介:相对于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一样,希望以后可以进行完善,毕竟瑕不掩瑜。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Computer Age Statistical Inference

Computer Age Statistical Inference

Bradley Efron、Trevor Hastie / Cambridge University Press / 2016-7-21 / USD 74.99

The twenty-first century has seen a breathtaking expansion of statistical methodology, both in scope and in influence. 'Big data', 'data science', and 'machine learning' have become familiar terms in ......一起来看看 《Computer Age Statistical Inference》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具