一個容錯的Gson新世界

1. 闖入背景:

公司項目中使用Gson框架對服務器傳過來的Json數據進行解析,而服務器後臺數據很大程度上是經過運營後臺人員配置。因爲各類緣由運營可能將某一字段類型配置錯誤,好比集合類型配置成字符串類型。雖然業務層會進行異常的捕獲,可是僅由於一個字段的錯誤,致使整個Json數據失效,因小失大,甚至可能會形成重大損失,好比直播間禮物牆,由於一個禮物的某一個字段的錯誤,致使整個禮物牆展現爲空,在線上環境這個算是重大事故了。因而,一個對基本類型容錯的Gson改造庫的需求油然而生,對於錯誤的數據以默認值填充。java

乾貨地址:類型容錯的Gsongit

2. Gson官方庫地址:

Github地址github

3. 前提說明

a. 當前分析的Gson版本號爲2.8.1。
b. Gson的處理過程主要分爲兩個流向,一個是序列化,將javabean對象轉化爲json字符串;另外一個是反序列化,將json字符串映射成javabean對象。
c. 這兩個流向處理前都有一個共同的操做,從傳入的java實例對象或者字節碼對象中獲取 TypeAdapter,對於序列化就經過Jsonwriter進行寫,對於反序列化就經過JsonReader進行讀,因此此篇只分析Gson讀的過程,寫處理操做流程同樣。json

4. Gson 關鍵列的梳理

  • Gson 開發者直接使用的類,只對輸入和輸出負責。
  • TypeToken 封裝「操做類」(Gson.fromJson(json,People.class、Gson.toJson(new People)) 兩處的People都是操做類)的類型。
  • TypeAdapter 直接操做序列化與反序列化的過程,因此該抽象類中存在read()和write方法。
  • TypeAdapterFactory 用於生產TypeAdapter的工廠類。
  • GsonReader和GsonWriter是Gson處理內容的包裝流,核心的操做有:
    • peek() 流中下一個須要處理的內容
    • nextName() 讀取json的key
    • nextString() 讀取一個String類型的value
    • nextInt() 讀取一個String類型的value
    • nextBoolean() 讀取一個Boolean類型的value
    • ...

5. 源碼分析。

從Gson.from(json, People.class) 突入數組

fromJson(json,Peolple.class)的調用鏈
  
  public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    Object object = fromJson(json, (Type) classOfT);
    return Primitives.wrap(classOfT).cast(object);
  }
  
  public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
    if (json == null) {
      return null;
    }
    StringReader reader = new StringReader(json);
    T target = (T) fromJson(reader, typeOfT);
    return target;
  }
  
  public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    JsonReader jsonReader = newJsonReader(json);
    T object = (T) fromJson(jsonReader, typeOfT);
    assertFullConsumption(object, jsonReader);
    return object;
  }
  
  public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
    boolean isEmpty = true;
    boolean oldLenient = reader.isLenient();
    reader.setLenient(true);
    try {
      reader.peek();
      isEmpty = false;
      TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
      TypeAdapter<T> typeAdapter = getAdapter(typeToken);
      T object = typeAdapter.read(reader);
      return object;
    } ...
  }
複製代碼

上面是從fromJson(String json, Class classOfT)切入,亦或者是從fromJson(JsonElement json, Class classOfT)也好,最終都是由 fromJson(JsonReader reader, Type typeOfT)處理。緩存

整個Json的解析過程分三步過程:

  • TypeToken對象的獲取
  • 根據TypeToken獲取TypeAdapter對象
  • 由TypeAdapter對象解析json字符串

根據以上的三步,咱們逐一突破


咱們先從簡單的入手,請記住咱們的例子:

gson.fromJson("hello gson",String.class)bash

1. TypeToken的獲取

public static TypeToken<?> get(Type type) {
    return new TypeToken<Object>(type);
  }
複製代碼

沒什麼好瞅的~ 看new吧!服務器

TypeToken(Type type) {
    this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type));
    this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type);
    this.hashCode = this.type.hashCode();
  }
複製代碼

採用契約式對傳入的type判空處理,而後獲取type的(type、rawType和hashcode),分別看看type和rawtype的獲取流程框架

1. type的獲取(type的華麗包裝)
public static Type canonicalize(Type type) {
    if (type instanceof Class) {
      Class<?> c = (Class<?>) type;
      return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;

    } else if (type instanceof ParameterizedType) {
      ParameterizedType p = (ParameterizedType) type;
      return new ParameterizedTypeImpl(p.getOwnerType(),
          p.getRawType(), p.getActualTypeArguments());

    } else if (type instanceof GenericArrayType) {
      GenericArrayType g = (GenericArrayType) type;
      return new GenericArrayTypeImpl(g.getGenericComponentType());

    } else if (type instanceof WildcardType) {
      WildcardType w = (WildcardType) type;
      return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());

    } else {
      // type is either serializable as-is or unsupported
      return type;
    }
複製代碼

進入條件的篩選,第一個if仍是好理解,後面的是什麼鬼? 不用着急,待我給施主梳理,以前Gson.from(json, People.class)的調用鏈中有一個fromJson(Reader json, Type typeOfT) ,用戶使用時的切入點若是是它就多是篩選狀況的其餘條件,此返回的type相對於對傳入的java類型進行的類型的從新包裝。ide

2. rawType的獲取(type的簡單粗暴說明)
public static Class<?> getRawType(Type type) {
    if (type instanceof Class<?>) {
      // type is a normal class.
      return (Class<?>) type;

    } else if (type instanceof ParameterizedType) {
      ParameterizedType parameterizedType = (ParameterizedType) type;

      // I'm not exactly sure why getRawType() returns Type instead of Class. // Neal isn't either but suspects some pathological case related
      // to nested classes exists.
      Type rawType = parameterizedType.getRawType();
      checkArgument(rawType instanceof Class);
      return (Class<?>) rawType;

    } else if (type instanceof GenericArrayType) {
      Type componentType = ((GenericArrayType)type).getGenericComponentType();
      return Array.newInstance(getRawType(componentType), 0).getClass();

    } else if (type instanceof TypeVariable) {
      // we could use the variable's bounds, but that won't work if there are multiple.
      // having a raw type that's more general than necessary is okay return Object.class; } else if (type instanceof WildcardType) { return getRawType(((WildcardType) type).getUpperBounds()[0]); } else { String className = type == null ? "null" : type.getClass().getName(); throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + "GenericArrayType, but <" + type + "> is of type " + className); } } 複製代碼

兩處對比的看,其實type和rawtype很類似,type經過類來包裝說明,而rawtype脫去華麗的衣服。type爲GenericArrayType的,把衣服一脫,赤身裸體的一看,擦,原來是個array數組,這就是rawtype。

2. TypeAdapter的獲取。

public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
    TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
    if (cached != null) {
      return (TypeAdapter<T>) cached;
    }

    Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
    boolean requiresThreadLocalCleanup = false;
    if (threadCalls == null) {
      threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
      calls.set(threadCalls);
      requiresThreadLocalCleanup = true;
    }

    // the key and value type parameters always agree
    FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
    if (ongoingCall != null) {
      return ongoingCall;
    }

    try {
      FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
      threadCalls.put(type, call);

      for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
      throw new IllegalArgumentException("GSON cannot handle " + type);
    }
複製代碼

若是緩存中沒有該Type對應TypeAdapter,就建立TypeAdapter。前面提過TypeAdapter是由TypeAdapterFactory建立的,因此有代碼:

for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
          call.setDelegate(candidate);
          typeTokenCache.put(type, candidate);
          return candidate;
        }
      }
複製代碼

遍歷全部的TypeAdapterFactory,若是該工廠能建立該Type的TypeAdapter就返回該TypeAdapter對象。

那麼重點來了,factories這麼多的TypeAdapterFactory是怎麼來了的?

在咱們new Gson的時候,就往factories中塞入了不一樣類型的TypeAdapterFactory,包括StringTypeAdapterFactory等等,代碼以下:

public Gson(xxx)
    {
        ...
        factories.add(TypeAdapters.STRING_FACTORY);
        factories.add(TypeAdapters.STRING_FACTORY);
        factories.add(TypeAdapters.INTEGER_FACTORY);
        factories.add(TypeAdapters.BOOLEAN_FACTORY);
        factories.add(TypeAdapters.BYTE_FACTORY);
        factories.add(TypeAdapters.SHORT_FACTORY);
        ...
    }
   
複製代碼

在遍歷factories過程當中經過create(this,type)方法來生成TypeAdapter。

咱們就以第一個STRING_FACTORY爲例先進行說明。
public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);
複製代碼

接着往下看

public static <TT> TypeAdapterFactory newFactory(
      final Class<TT> type, final TypeAdapter<TT> typeAdapter) {
    return new TypeAdapterFactory() {
      @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
      @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
        return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
      }
      @Override public String toString() {
        return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
      }
    };
  }
複製代碼

STRING_FACTORY = newFactory(String.class, STRING)的時候,STRING就是處理String類型的TypeAdapter,STRING_FACTORY中的create方法就是判斷須要處理的類型是否是String類型的,若是是就返回STRING,不然返回null,即該類型不用STRING來處理。

總的來講,在建立Gson的實例對象時,建立TypeAdapterFactory的集合。每種TypeAdapterFactory實例包含能處理的Type類型和Type類型的TypeAdapter,不能處理的Type類型返回的TypeAdapter爲null,因此在遍歷factories過程當中有:

for (TypeAdapterFactory factory : factories) {
        TypeAdapter<T> candidate = factory.create(this, type);
        if (candidate != null) {
            ...
          return candidate;
        }
      }
複製代碼

3. 由TypeAdapter對象解析json字符串

咱們回到最初的代碼:

TypeToken<T> typeToken = (TypeToken<T>)TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
複製代碼

STRING就是處理String類型的TypeAdapter,而後咱們看它的read()方法。

public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
    @Override
    public String read(JsonReader in) throws IOException {
      JsonToken peek = in.peek();
      if (peek == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      /* coerce booleans to strings for backwards compatibility */
      if (peek == JsonToken.BOOLEAN) {
        return Boolean.toString(in.nextBoolean());
      }
      return in.nextString();
    }
   ...
  };
複製代碼

到這裏位置,咱們就將gson.fromJson("hello gson",String.class)的String類型「hello gson」返回。


剛剛是隻是牛刀小試,咱們的主材料來了,看看有多豐盛...

Gson.from("{ "name": "zhangsan", "age": 15, "grade": [ 95, 98 ] }", Student.class)

咱們從新走剛剛的流程,看看怎麼處理的

Step one : 獲取TypeToken

這一步沒有什麼不同凡響

Step Two: TypeAdapter的獲取。

factories中包含了不少基本類型的TypeAdapterFactory,同時也包含用戶自定義的類型Factory,看源碼:

// type adapters for composite and user-defined types
        
    factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
    factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
    this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
    factories.add(jsonAdapterFactory);
    factories.add(TypeAdapters.ENUM_FACTORY);
    factories.add(new ReflectiveTypeAdapterFactory(constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
複製代碼

此處咱們能匹配上的是ReflectiveTypeAdapterFactory,而後咱們看它的create()方法,關鍵的地方到了!!!

@Override public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
    Class<? super T> raw = type.getRawType();

    if (!Object.class.isAssignableFrom(raw)) {
      return null; // it's a primitive! } ObjectConstructor<T> constructor = constructorConstructor.get(type); return new Adapter<T>(constructor, getBoundFields(gson, type, raw)); } 複製代碼

a. constructorConstructor 獲取Student類的構造器
b. getBoundFields()經過反射獲取Student每個字段的的TypeAdapter,而且包裝到Map<String, BoundField>中,後面會講解getBoundFields()的方法。

Step Three 經過TypeAdapter的read()輸出對象

@Override public T read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }

      T instance = constructor.construct();

      try {
        in.beginObject();
        while (in.hasNext()) {
          String name = in.nextName();
          BoundField field = boundFields.get(name);
          if (field == null || !field.deserialized) {
            in.skipValue();
          } else {
            field.read(in, instance);
          }
        }
      } catch (IllegalStateException e) {
        throw new JsonSyntaxException(e);
      } catch (IllegalAccessException e) {
        throw new AssertionError(e);
      }
      in.endObject();
      return instance;
    }
複製代碼

到了這一步就彷佛海闊天空了,經過傳入的構造器建立Student類的實例,在JsonReader進行處理,in.beginObject()至關於跳過「{」,in.endObject()至關於跳過「}」,其中經過in.hasNext()判斷是否處理完成。 在in.nextName()讀取json字符串中的key值,而後在boundFields根據key獲取對應的BoundField ,最後調用BoundField.read(in,instance)去處理細節,即每一個字段的映射,咱們看一下內部的細節:

new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
      ...
      @Override void read(JsonReader reader, Object value)
          throws IOException, IllegalAccessException {
        Object fieldValue = typeAdapter.read(reader);
        if (fieldValue != null || !isPrimitive) {
          field.set(value, fieldValue);
        }
      }
     
     ...
    };
複製代碼

當Filed都處理完成後,instance實例的每個須要處理的字段都賦值成功,最終將這個對象return出去。


細節說明:

a. getBoundFields()

private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
    Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
    if (raw.isInterface()) {
      return result;
    }

    Type declaredType = type.getType();
    while (raw != Object.class) {
      Field[] fields = raw.getDeclaredFields();
      for (Field field : fields) {
        boolean serialize = excludeField(field, true);
        boolean deserialize = excludeField(field, false);
        if (!serialize && !deserialize) {
          continue;
        }
        field.setAccessible(true);
        Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
        List<String> fieldNames = getFieldNames(field);
        BoundField previous = null;
        for (int i = 0, size = fieldNames.size(); i < size; ++i) {
          String name = fieldNames.get(i);
          if (i != 0) serialize = false; // only serialize the default name
          BoundField boundField = createBoundField(context, field, name,
              TypeToken.get(fieldType), serialize, deserialize);
          BoundField replaced = result.put(name, boundField);
          if (previous == null) previous = replaced;
        }
        if (previous != null) {
          throw new IllegalArgumentException(declaredType
              + " declares multiple JSON fields named " + previous.name);
        }
      }
      type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
      raw = type.getRawType();
    }
    return result;
  }
複製代碼

遍歷Student類的每個字段,遍歷過程當中作了兩件事情:

  • a. 該字段可否被序列化和反序列化,若是都不行就沒有必要處理該字段,主要經過註解和排除器(Excluder)進行判斷。
  • b. 對字段進行BoundField的包裝。

b. JsonReader.doPeek()

int doPeek() throws IOException {
    int peekStack = stack[stackSize - 1];
    if (peekStack == JsonScope.EMPTY_ARRAY) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
    } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
      // Look for a comma before the next element.
      int c = nextNonWhitespace(true);
      switch (c) {
      case ']':
        return peeked = PEEKED_END_ARRAY;
      case ';':
        checkLenient(); // fall-through
      case ',':
        break;
      default:
        throw syntaxError("Unterminated array");
      }
    } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
      stack[stackSize - 1] = JsonScope.DANGLING_NAME;
      // Look for a comma before the next element.
      if (peekStack == JsonScope.NONEMPTY_OBJECT) {
        int c = nextNonWhitespace(true);
        switch (c) {
        case '}':
          return peeked = PEEKED_END_OBJECT;
        case ';':
          checkLenient(); // fall-through
        case ',':
          break;
        default:
          throw syntaxError("Unterminated object");
        }
      }
      int c = nextNonWhitespace(true);
      switch (c) {
      case '"':
        return peeked = PEEKED_DOUBLE_QUOTED_NAME;
      case '\'': checkLenient(); return peeked = PEEKED_SINGLE_QUOTED_NAME; case '}': if (peekStack != JsonScope.NONEMPTY_OBJECT) { return peeked = PEEKED_END_OBJECT; } else { throw syntaxError("Expected name"); } default: checkLenient(); pos--; // Don't consume the first character in an unquoted string.
        if (isLiteral((char) c)) {
          return peeked = PEEKED_UNQUOTED_NAME;
        } else {
          throw syntaxError("Expected name");
        }
      }
    } else if (peekStack == JsonScope.DANGLING_NAME) {
      stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
      // Look for a colon before the value.
      int c = nextNonWhitespace(true);
      switch (c) {
      case ':':
        break;
      case '=':
        checkLenient();
        if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
          pos++;
        }
        break;
      default:
        throw syntaxError("Expected ':'");
      }
    } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
      if (lenient) {
        consumeNonExecutePrefix();
      }
      stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
    } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
      int c = nextNonWhitespace(false);
      if (c == -1) {
        return peeked = PEEKED_EOF;
      } else {
        checkLenient();
        pos--;
      }
    } else if (peekStack == JsonScope.CLOSED) {
      throw new IllegalStateException("JsonReader is closed");
    }

    int c = nextNonWhitespace(true);
    switch (c) {
    case ']':
      if (peekStack == JsonScope.EMPTY_ARRAY) {
        return peeked = PEEKED_END_ARRAY;
      }
      // fall-through to handle ",]"
    case ';':
    case ',':
      // In lenient mode, a 0-length literal in an array means 'null'.
      if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
        checkLenient();
        pos--;
        return peeked = PEEKED_NULL;
      } else {
        throw syntaxError("Unexpected value");
      }
    case '\'': checkLenient(); return peeked = PEEKED_SINGLE_QUOTED; case '"': return peeked = PEEKED_DOUBLE_QUOTED; case '[': return peeked = PEEKED_BEGIN_ARRAY; case '{': return peeked = PEEKED_BEGIN_OBJECT; default: pos--; // Don't consume the first character in a literal value. } int result = peekKeyword(); if (result != PEEKED_NONE) { return result; } result = peekNumber(); if (result != PEEKED_NONE) { return result; } if (!isLiteral(buffer[pos])) { throw syntaxError("Expected value"); } checkLenient(); return peeked = PEEKED_UNQUOTED; } 複製代碼

該操做邏輯處理較強,主要工做分爲3點:

  • json的格式校驗,格式不合法拋出異常
  • 根據當前的操做,決定下一步的操做方式
  • 流中下一部分的內容類型
相關文章
相關標籤/搜索