json处理库五花八门,本文适合使用gson的老铁。
gson也算是老牌json处理库了,在某些性能方面被弟弟们摩擦,不过因为兼容性不错(公司再用)的原因,还是要改改它的脾气。
问题:PHP同学过于奔放,什么乱七八糟字段都有,一会"null",一会"",一会int给你传个字符串,一会数组当字典,遇到过太多因为某个字段解析失败导致一个请求整体解析失败的问题了。
又不能打人家,只能自己不受影响了。
入口:fromJson("我是json", JsonBean.class)
gson方法不多,但一眼看下去看不到重点,索性直接对json解码,然后debug进去,fromJson调用如下:
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调用getAdapter去获取所传入类型JsonBean这个类所对应的类型适配器,然后用这个适配器去解析。
下面进入getAdapter:
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();
}
}
}
方法开始,根据类型查找类型适配器,先从typeTokenCache查找,找不到就创建一个并扔到typeTokenCache里面。创建是由一组factories来创建的,逻辑是只要一个factory创建不为空,就把factory创建的类型适配器返回。
追踪factories的源头,发现原来他是在Gson的构造方法中初始化的,其中包含了大量的工厂:
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
// ...
factories.add(new ReflectiveTypeAdapterFactory(
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
// ...
this.factories = Collections.unmodifiableList(factories);
从这里我们可以了解到:Gson在初始化时基本上为所有能用到的类型都创建了用来创建类型适配器的类型适配器工厂,这句有点绕,理解下。我们传入的class,就是根据某一个工厂创建的适配器去解析的。
那么是哪个适配器解析的我们的参数呢?在Gson#fromJson方法中,断点进入到typeAdapter.read中去,发现解析我们的适配器如下:
public static final class Adapter<T> extends TypeAdapter<T> {
private final ObjectConstructor<T> constructor;
private final Map<String, BoundField> boundFields;
Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
this.constructor = constructor;
this.boundFields = boundFields;
}
@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;
}
}
这个适配器是ReflectiveTypeAdapterFactory的内部类。可以看到,这个类有个成员boundFields,包含了他所有的成员,在read方法中,遍历所有的成员,对其进行赋值,调用boundFields.read方法,boundField的实现为一个匿名类,在createBoundField方法中:
private ReflectiveTypeAdapterFactory.BoundField createBoundField(
final Gson context, final Field field, final String name,
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
// special casing primitives here saves ~5% on Android...
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
TypeAdapter<?> mapped = null;
if (annotation != null) {
mapped = jsonAdapterFactory.getTypeAdapter(
constructorConstructor, context, fieldType, annotation);
}
final boolean jsonAdapterPresent = mapped != null;
if (mapped == null) mapped = context.getAdapter(fieldType);
final TypeAdapter<?> typeAdapter = mapped;
return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
@SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
@Override void write(JsonWriter writer, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = field.get(value);
TypeAdapter t = jsonAdapterPresent ? typeAdapter
: new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
t.write(writer, fieldValue);
}
@Override void read(JsonReader reader, Object value)
throws IOException, IllegalAccessException {
Object fieldValue = typeAdapter.read(reader);
if (fieldValue != null || !isPrimitive) {
field.set(value, fieldValue);
}
}
@Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
if (!serialized) return false;
Object fieldValue = field.get(value);
return fieldValue != value; // avoid recursion for example for Throwable.cause
}
};
}
在它的read方法中,首先根据字段的类型,调用另一个适配器,获取到的值,使用反射的方法set到对象上。
通过追踪了解到,这个read方法对应的位置如果是个对象,这个typeAdapter就会实例化为ReflectiveTypeAdapterFactory#Adapter,由此实现了内部类的层层嵌套。
到这里我们的结论:
所有的类型的解析,用的无非就是用的Gson构造方法中创建的那几个factory生成的适配器,只要我们按照类型去自己定义适配器,就能够改变指定的类型的处理行为。
上面看那么多,其实是今天遇到了一个问题,就是服务端在本是String类型的字段,下发了一个Map。
那我们就针对这个状况撸一个适配器,然后通过registerAdapter方法注册到Gson对象。
看看抛异常的地方,发生在这个方法JsonReader#nextString:
public String nextString() throws IOException {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
String result;
if (p == PEEKED_UNQUOTED) {
result = nextUnquotedValue();
} else if (p == PEEKED_SINGLE_QUOTED) {
result = nextQuotedValue('\'');
} else if (p == PEEKED_DOUBLE_QUOTED) {
result = nextQuotedValue('"');
} else if (p == PEEKED_BUFFERED) {
result = peekedString;
peekedString = null;
} else if (p == PEEKED_LONG) {
result = Long.toString(peekedLong);
} else if (p == PEEKED_NUMBER) {
result = new String(buffer, pos, peekedNumberLength);
pos += peekedNumberLength;
} else {
throw new IllegalStateException("Expected a string but was " + peek() + locationString());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return result;
}
如果peek到Map的时候能不走到else就可以不抛异常了,可惜的是Gson是final类型的,我们没法通过覆写newJsonReader的方法干预JsonReader的创建。那么我们就让他不进入nextString好了。
来到JsonReader#nextString的调用者,是TypeAdapters类的内部类:
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();
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
};
我们参考默认的String类型的适配器重写一个再注册到Gson就完事了。
代码:
// 参考String类型的默认解析器实现TypeAdapters#STRING
public static class StrongStringTypeAdapter extends 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());
}
// 跳过
if (skipInvalidContent(in)){
return null;
}
return in.nextString();
}
@Override
public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
}
private static boolean skipInvalidContent(JsonReader in) throws IOException {
boolean handled = false;
JsonToken peek = in.peek();
if (peek == JsonToken.BEGIN_OBJECT){
in.beginObject();
while (in.hasNext()){
in.nextName();
in.skipValue();
}
in.endObject();
handled = true;
}
if (peek == JsonToken.BEGIN_ARRAY){
in.beginArray();
while (in.hasNext()){
in.nextName();
in.skipValue();
}
in.endArray();
handled = true;
}
return handled;
}
至此,PHP的屁股擦完了。
只是提供一个思路,后面的大家自己补充,毕竟授人以鱼不如授人以渔。