在用Gson解析时传过来的Json
串时,若是将其解析为对象A,而这个对象A继承了对象B。这两个对象都有属性名为name
的值,那么在进行解析的时候就会报以下错误。java
Exception in thread "main" java.lang.IllegalArgumentException: class Practice.Day12.Student2 declares multiple JSON fields named name at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:172) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102) at com.google.gson.Gson.getAdapter(Gson.java:458) at com.google.gson.Gson.fromJson(Gson.java:926) at com.google.gson.Gson.fromJson(Gson.java:892) at com.google.gson.Gson.fromJson(Gson.java:841) at com.google.gson.Gson.fromJson(Gson.java:813) at Practice.Day12.TestSetField.main(TestSetField.java:39)
报错的代码以下数组
String str = "{\"name\":\"BuXueWuShu\"}"; Gson gson = new Gson(); gson.fromJson(str,Student2.class);
通过查询在将其解析为对象的时候用的Java技术是反射,觉得是反射的缘由不能赋值,而后写了以下的小例子发现是经过反射可以将值赋给name
的。例子以下app
public class Student{ private String name ; -----get.set方法 } public class Student2 extends Student{ private String name ; -----get.set方法 }
而后调用反射赋值ide
Field name = cl.getDeclaredField("name"); Student2 student = (Student2) cl.newInstance(); name.setAccessible(true); name.set(student,"bb"); System.out.println(student.getName());
发现确实可以取到name
的值。函数
不是反射的缘由,那么就是Gson在进行解析的时候作了处理,因此也就开启这一次的Gson源码解析之旅oop
直接Debug从gson.fromJson(str,Student2.class);
中进去,发现前面有好多的重载函数,可是最终都是调用了post
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; } catch (EOFException e) { /* * For compatibility with JSON 1.5 and earlier, we return null for empty * documents instead of throwing. */ if (isEmpty) { return null; } throw new JsonSyntaxException(e); } catch (IllegalStateException e) { throw new JsonSyntaxException(e); } catch (IOException e) { // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException throw new JsonSyntaxException(e); } catch (AssertionError e) { throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e); } finally { reader.setLenient(oldLenient); } }
其中重要的就这两个方法ui
TypeAdapter<T> typeAdapter = getAdapter(typeToken); T object = typeAdapter.read(reader);
上面的getAdapter
是获取合适的Adapter。点进去之后发现this
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 (" + GsonBuildConfig.VERSION + ") cannot handle " + type); } finally { threadCalls.remove(type); if (requiresThreadLocalCleanup) { calls.remove(); } } }
发现重要的部分是google
建立
TypeAdapterFactory
的集合。每种TypeAdapterFactory
实例包含能处理的Type类型和Type类型的TypeAdapter
,不能处理的Type类型返回的TypeAdapter
为null
for (TypeAdapterFactory factory : factories) { TypeAdapter<T> candidate = factory.create(this, type); if (candidate != null) { call.setDelegate(candidate); typeTokenCache.put(type, candidate); return candidate; } }
其中TypeAdapterFactory
是在Gson的构造函数块中直接加载好的,代码以下
Gson() { List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>(); // built-in type adapters that cannot be overridden factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); factories.add(ObjectTypeAdapter.FACTORY); // the excluder must precede all adapters that handle user-defined types factories.add(excluder); // users' type adapters factories.addAll(factoriesToBeAdded); // type adapters for basic platform types factories.add(TypeAdapters.STRING_FACTORY); factories.add(TypeAdapters.INTEGER_FACTORY); factories.add(TypeAdapters.BOOLEAN_FACTORY); factories.add(TypeAdapters.BYTE_FACTORY); --------中间太多省略 factories.add(TimeTypeAdapter.FACTORY); factories.add(SqlDateTypeAdapter.FACTORY); factories.add(TypeAdapters.TIMESTAMP_FACTORY); factories.add(ArrayTypeAdapter.FACTORY); factories.add(TypeAdapters.CLASS_FACTORY); this.factories = Collections.unmodifiableList(factories); }
只要是转化为对象的,那么其Adapter就是ReflectiveTypeAdapterFactory
,而后看其creat()
方法
@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)); }
重要部分在return中的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) { --这两个字段可否被序列化和反序列化,若是都不行就没有必要处理该字段,主要经过注解和排除器(Excluder)进行判断。 boolean serialize = excludeField(field, true); boolean deserialize = excludeField(field, false); if (!serialize && !deserialize) { continue; } accessor.makeAccessible(field); Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); --遍历出全部的属性名称 List<String> fieldNames = getFieldNames(field); BoundField previous = null; --开始装饰返回结果result 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); } } --这两句是得到其父类的Class对象 type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass())); raw = type.getRawType(); } return result; }
而后在这里面发现了所抛异常的地方。首先说一下这个方法是干什么用的,而后再分析为何会抛异常。
getBoundFields()
方法返回的主要就是当前类的属性和属性类型,这些数据就是用于后续Json数据解析的。
其中raw != Object.class
在while循环中只要不是Object类那么就一直解析,即将子类的父类所有解析出来除了Object类。避免遗漏父类的属性值。此时发现报错的关键点在于下面这一句。
BoundField replaced = result.put(name, boundField);
result
是一个 Map<String, BoundField>
Map对象。而调用put()
方法会有一个返回值。若是在Map中根据name
找不到值,那么就返回null,若是在Map中根据name
能找到值,那么就返回此值。举个例子
Map<String ,String > map = new HashMap<>(); String str1 = map.put("1","1"); String str2 = map.put("1","2"); System.out.println(str1); System.out.println(str2);
输出为
null 1
因此报异常的缘由就找到了。在第二次父类在往result中放值的时候已经有了name
的值,因此就会返回子类的BoundField
而且将其赋给previous
此时在后面判断previous
不为null因此就抛异常了。因此就知道若是将解析换为以下就会成功。将其类型传入为父类。
gson.fromJson(str,Student.class);
而后咱们接着往下查看Gson是如何进行解析的。在上面咱们提到了有两句是重要的
TypeAdapter<T> typeAdapter = getAdapter(typeToken); T object = typeAdapter.read(reader);
第一句已经解析完了,得到相应的Adapter
而后调用相应Adapter
的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()) { -- 得到Json中相应key值 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; }
而后在field.read(in, instance);
中代码以下
@Override void read(JsonReader reader, Object value) throws IOException, IllegalAccessException { Object fieldValue = typeAdapter.read(reader); if (fieldValue != null || !isPrimitive) { field.set(value, fieldValue); } }
发现其最终是调用了Field
的set()
方法对相应的属性值进行了赋值。
解析到了这相信应该还有一个疑问,Gson是如何将字符串中的对应的key
和value
取出来的。
Key
和Value
解析取出Key
和Value
都是在Adapter
的read()
方法中作的。其中取key
是以下的方法作的
String name = in.nextName();
而后最后调用了以下方法。
private String nextQuotedValue(char quote) throws IOException { // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access. char[] buffer = this.buffer; StringBuilder builder = null; while (true) { int p = pos; int l = limit; /* the index of the first character not yet appended to the builder. */ int start = p; while (p < l) { int c = buffer[p++]; if (c == quote) { pos = p; int len = p - start - 1; if (builder == null) { return new String(buffer, start, len); } else { builder.append(buffer, start, len); return builder.toString(); } } else if (c == '\\') { pos = p; int len = p - start - 1; if (builder == null) { int estimatedLength = (len + 1) * 2; builder = new StringBuilder(Math.max(estimatedLength, 16)); } builder.append(buffer, start, len); builder.append(readEscapeCharacter()); p = pos; l = limit; start = p; } else if (c == '\n') { lineNumber++; lineStart = p; } } if (builder == null) { int estimatedLength = (p - start) * 2; builder = new StringBuilder(Math.max(estimatedLength, 16)); } builder.append(buffer, start, p - start); pos = p; if (!fillBuffer(1)) { throw syntaxError("Unterminated string"); } } }
咱们发现Gson将Json串放入了char[]
数组中,而后上面的取数据的过程能够抽象以下图
在刚开始的时候设置其start
索引为2,limit
为字符串的长度
而后经过int c = buffer[p++];
不断得到值。获取值之后进行判断if (c == quote)
其中的quote
为"
的值,若是索引走到了"
引号处,那么就进行
pos = p; int len = p - start - 1; if (builder == null) { return new String(buffer, start, len); }
就在此进行字符串的截取操做,将其返回,而后记录其索引值,取完key
之后的索引值以下
而后在进行取value
时调用了以下方法Object fieldValue = typeAdapter.read(reader);
而后在其内部对pos
进行了包装此时的pos为
其取value
的方法和取key
的方法是同样的。