Gson
在用Gson解析时传过来的Json
串时,如果将其解析为对象A,而这个对象A继承了对象B。这两个对象都有属性名为name
的值,那么在进行解析的时候就会报如下错误。
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
的。例子如下
public class Student{
private String name ;
-----get.set方法
}
public class Student2 extends Student{
private String name ;
-----get.set方法
}
然后调用反射赋值
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源码解析之旅
源码解析
直接Debug从gson.fromJson(str,Student2.class);
中进去,发现前面有好多的重载函数,但是最终都是调用了
public 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 typeToken = (TypeToken) TypeToken.get(typeOfT);
TypeAdapter 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);
}
}
其中重要的就这两个方法
TypeAdapter typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
上面的getAdapter
是获取合适的Adapter。点进去以后发现
public TypeAdapter getAdapter(TypeToken type) {
TypeAdapter> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
if (cached != null) {
return (TypeAdapter) cached;
}
Map, FutureTypeAdapter>> threadCalls = calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap, FutureTypeAdapter>>();
calls.set(threadCalls);
requiresThreadLocalCleanup = true;
}
// the key and value type parameters always agree
FutureTypeAdapter ongoingCall = (FutureTypeAdapter) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
try {
FutureTypeAdapter call = new FutureTypeAdapter();
threadCalls.put(type, call);
for (TypeAdapterFactory factory : factories) {
TypeAdapter 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();
}
}
}
发现重要的部分是
创建
TypeAdapterFactory
的集合。每种TypeAdapterFactory
实例包含能处理的Type类型和Type类型的TypeAdapter
,不能处理的Type类型返回的TypeAdapter
为null
for (TypeAdapterFactory factory : factories) {
TypeAdapter candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
其中TypeAdapterFactory
是在Gson的构造函数块中直接加载好的,代码如下
Gson() {
List factories = new ArrayList();
// 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 TypeAdapter create(Gson gson, final TypeToken type) {
Class super T> raw = type.getRawType();
if (!Object.class.isAssignableFrom(raw)) {
return null; // it's a primitive!
}
ObjectConstructor constructor = constructorConstructor.get(type);
return new Adapter(constructor, getBoundFields(gson, type, raw));
}
重要部分在return中的getBoundFields()
方法。点进去代码如下
private Map getBoundFields(Gson context, TypeToken> type, Class> raw) {
Map result = new LinkedHashMap();
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 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
Map对象。而调用put()
方法会有一个返回值。如果在Map中根据name
找不到值,那么就返回null,如果在Map中根据name
能找到值,那么就返回此值。举个例子
Map 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 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
取出来的。
Gson如何取出对应的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
的方法是一样的。
参考文章
- https://www.jianshu.com/p/789a1634b3c2
- https://www.jianshu.com/p/e38e7cb5076c
- https://juejin.im/post/5b8a2f5af265da43445f75ee