标签:
去公司实习了,没多少时间更博客了,距离上一篇博客也有一个来月了。看标题,应该可以看出,这篇文章是讲一个坑,以及如何填坑。
坑是什么?有两个坑,其一是fastjson的bug,其二是不规范的json字符串。如何填坑,不要着急,后文详细说明。
首先,我们看一个json字符串
{
"doubleParam": 4.875,
"floatParam": 2.76,
"extra": {
"doubleParam": 12.23,
"floatParam": 12.54
},
}
这是一个比较规范的json字符串,如果我们使用fastjson解析它,基本上是不会有任何问题的。
解析之前,我们需要根据这个json字符串生成bean,当然你可以选择Android Studio中的插件Gson Formattor自动生成bean。最终的两个bean如下所示,为了代码简单,我直接将属性设为了public,省略了getter和setter方法。
public class Extra {
public double doubleParam;
public float floatParam;
@Override
public String toString() {
return "Extra{" +
"doubleParam=" + doubleParam +
", floatParam=" + floatParam +
‘}‘;
}
}
public class Bean {
public double doubleParam;
public float floatParam;
public Extra extra;
@Override
public String toString() {
return "Bean{" +
"doubleParam=" + doubleParam +
", floatParam=" + floatParam +
", extra=" + extra +
‘}‘;
}
}
然后我们需要一个泛型的Json工具类,对于这个工具类,你要做的就是直接使用即可。
public class JsonUtil {
/**
* 将对象转换成json
*
* @param src 对象
* @return 返回json字符串
* @throws Exception
*/
public static <T> String toJson(T src) {
try {
return src instanceof String ? (String) src : JSON.toJSONString(src);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json通过类型转换成对象
*
* @param json json字符串
* @param clazz 泛型类型
* @return 返回对象, 失败返回NULL
*/
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return clazz.equals(String.class) ? (T) json : JSON.parseObject(json, clazz);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json通过类型转换成对象
*
* @param json json字符串
* @param typeReference 引用类型
* @return 返回对象
*/
public static <T> T fromJson(String json, TypeReference<?> typeReference) {
try {
return (T) (typeReference.getType().equals(String.class) ? json
: JSON.parseObject(json, typeReference));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
我们将这个json字符串定义成一个常量,方便使用。
public class Constant {
public static final String JSON = "{\"doubleParam\":4.875,\"extra\":{\"doubleParam\":12.23,\"floatParam\":12.54},\"floatParam\":2.76}";
}
接下来就是开始解析了,直接使用JsonUtil工具类进行解析即可。
Bean bean = JsonUtil.fromJson(Constant.JSON, Bean.class);
System.out.println(bean);
只要能够正常的输出结果,基本上就是解析成功了。输出结果如下
Bean{doubleParam=4.875, floatParam=2.76, extra=Extra{doubleParam=12.23, floatParam=12.54}}
以上过程,看上去十分完美,但是现实往往是残酷的。第一个坑即将来临。客户端使用的json字符串往往是来自服务器端,而客户端与服务器端的json规范有时候往往没有统一,导致了一方必然需要兼容另一方。比如,服务器端返回的json字符串,无论是否是字符串类型,有时候都会加上一个引号,即使你的属性值是int,float,double,boolean类型,服务器端给你返回的也是一个字符串。 就像下面这样
{
"doubleParam": "4.875",
"extra": {
"doubleParam": "12.23",
"floatParam": "12.54"
},
"floatParam": "2.76"
}
上面这个json字符串其实是不规范的,因为不论什么数据类型,它都加上了引号,好在使用fastjson解析这样的数据时,也是可以正常解析的。而且解析结果十分完美,没有一丝的错误。那么为什么说是个坑呢。
服务器返回上面这种都是字符串类型的其实都好说,不会对解析结果造成任何影响,但是往往现实也不是这样的,有时候某一个值没有,服务器理论上应该返回一个默认值,比如int类型应该返回”0”,然而,实际上你接受到的json字符串却是这样的。
{
"doubleParam": "",
"extra": {
"doubleParam": "12.23",
"floatParam": ""
},
"floatParam": "2.76"
}
可以看到,最外层的doubleParam值为空字符串,内部的extra对象中的floatParam值也为空字符串。其实即使是空,它们也应该被赋值一个默认值0,这样,在客户端解析时不会发生任何错误。问题就出在这个空字符串。一旦使用了空字符串,在客户端解析时就会引发致命的错误。而这个错误的原因来自fastjson的一个Bug,然而我并不知道作者为什么没有修复这个bug。现在我们来解析一下,看看会发生什么错误。代码还是原来的配方。但是结果却发生了翻天覆地的变化。程序报了一个错误,并且最终我们解析得到的bean是一个null。
com.alibaba.fastjson.JSONException: set property error, doubleParam
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:97)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:43)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:400)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:310)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:115)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:548)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:215)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:191)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:150)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:255)
at cn.edu.zafu.demo.fastjson.JsonUtil.fromJson(JsonUtil.java:66)
at cn.edu.zafu.demo.Main.main(Main.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalArgumentException: Can not set double field cn.edu.zafu.demo.bean.Bean.doubleParam to null value
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeDoubleFieldAccessorImpl.set(UnsafeDoubleFieldAccessorImpl.java:80)
at java.lang.reflect.Field.set(Field.java:764)
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:95)
... 16 more
null
发生错误的原因这里不追究,如果你想知道原因,跟进源码查看一下你就会知道为什么会产生这个异常。当前,我使用的fastjson的版本是
compile ‘com.alibaba:fastjson:1.1.46.android‘
应该是android中的最新版,而且我也查过了官方的wiki中的release日志,在1.1.45版本里面明确说明了修复过这个bug,但是实际上还是存在。
修复数值类型byte、short、int、long、float、double、boolean的值为null时,反序列化失败的问题。
确实,其他数据类型,比如int,long,boolean是不在在这个问题的,唯独float和double存在这个问题。于是我们需要对症下药。
我们来翻翻fastjson的源码。在parser包下面有一个ParserConfig,里面有这么一段代码。
public FieldDeserializer createFieldDeserializer(ParserConfig mapping, Class<?> clazz, FieldInfo fieldInfo) {
Class<?> fieldClass = fieldInfo.getFieldClass();
if (fieldClass == boolean.class || fieldClass == Boolean.class) {
return new BooleanFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == int.class || fieldClass == Integer.class) {
return new IntegerFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == long.class || fieldClass == Long.class) {
return new LongFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == String.class) {
return new StringFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == List.class || fieldClass == ArrayList.class) {
return new ArrayListTypeFieldDeserializer(mapping, clazz, fieldInfo);
}
return new DefaultFieldDeserializer(mapping, clazz, fieldInfo);
}
可以看到,boolean,int,long都设置了反序列化用的类,而float和double却没有,于是,我们就需要对这个方法进行改造。我们新建一个PatchParserConfig类继承ParserConfig类。我们预料的结果应该是这样的。
public class PatchParserConfig extends ParserConfig {
@Override
public FieldDeserializer createFieldDeserializer(ParserConfig mapping, Class<?> clazz, FieldInfo fieldInfo) {
Class<?> fieldClass = fieldInfo.getFieldClass();
if (fieldClass == boolean.class || fieldClass == Boolean.class) {
return new BooleanFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == int.class || fieldClass == Integer.class) {
return new IntegerFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == long.class || fieldClass == Long.class) {
return new LongFieldDeserializer(mapping, clazz, fieldInfo);
}
// patch
if (fieldClass == float.class || fieldClass == Float.class) {
return new FloatFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == double.class || fieldClass == Double.class) {
return new DoubleFieldDeserializer(mapping, clazz, fieldInfo);
}
// patch
if (fieldClass == String.class) {
return new StringFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == List.class || fieldClass == ArrayList.class) {
return new ArrayListTypeFieldDeserializer(mapping, clazz, fieldInfo);
}
return new DefaultFieldDeserializer(mapping, clazz, fieldInfo);
}
}
很明显的我们增加了下面的几行代码
// patch
if (fieldClass == float.class || fieldClass == Float.class) {
return new FloatFieldDeserializer(mapping, clazz, fieldInfo);
}
if (fieldClass == double.class || fieldClass == Double.class) {
return new DoubleFieldDeserializer(mapping, clazz, fieldInfo);
}
// patch
而FloatFieldDeserializer类和DoubleFieldDeserializer都是哪来的呢。这个很简单,照样画葫芦就可以了,在parser包里的deserializer包种有各种反序列化用的类。我们选择其中一个照样画葫芦就ok了。比如我们选择LongFieldDeserializer,其代码如下
public class LongFieldDeserializer extends FieldDeserializer {
private final ObjectDeserializer fieldValueDeserilizer;
public LongFieldDeserializer(ParserConfig mapping, Class<?> clazz, FieldInfo fieldInfo){
super(clazz, fieldInfo);
fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
}
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
Long value;
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_INT) {
long val = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
if (object == null) {
fieldValues.put(fieldInfo.getName(), val);
} else {
setValue(object, val);
}
return;
} else if (lexer.token() == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = parser.parse();
value = TypeUtils.castToLong(obj);
}
if (value == null && getFieldClass() == long.class) {
// skip
return;
}
if (object == null) {
fieldValues.put(fieldInfo.getName(), value);
} else {
setValue(object, value);
}
}
public int getFastMatchToken() {
return fieldValueDeserilizer.getFastMatchToken();
}
}
按照这个模子,很容易的就出来了我们的两个类。
public class DoubleFieldDeserializer extends FieldDeserializer {
private final ObjectDeserializer fieldValueDeserilizer;
public DoubleFieldDeserializer(ParserConfig mapping, Class<?> clazz, FieldInfo fieldInfo) {
super(clazz, fieldInfo);
fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
}
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
Double value;
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_INT) {
long val = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
if (object == null) {
fieldValues.put(fieldInfo.getName(), val);
} else {
setValue(object, val);
}
return;
} else if (lexer.token() == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = parser.parse();
value = TypeUtils.castToDouble(obj);
}
if (value == null && getFieldClass() == double.class) {
// skip
return;
}
if (object == null) {
fieldValues.put(fieldInfo.getName(), value);
} else {
setValue(object, value);
}
}
@Override
public int getFastMatchToken() {
return fieldValueDeserilizer.getFastMatchToken();
}
}
public class FloatFieldDeserializer extends FieldDeserializer {
private final ObjectDeserializer fieldValueDeserilizer;
public FloatFieldDeserializer(ParserConfig mapping, Class<?> clazz, FieldInfo fieldInfo) {
super(clazz, fieldInfo);
fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
}
@Override
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
Float value;
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_INT) {
long val = lexer.longValue();
lexer.nextToken(JSONToken.COMMA);
if (object == null) {
fieldValues.put(fieldInfo.getName(), val);
} else {
setValue(object, val);
}
return;
} else if (lexer.token() == JSONToken.NULL) {
value = null;
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = parser.parse();
value = TypeUtils.castToFloat(obj);
}
if (value == null && getFieldClass() == float.class) {
// skip
return;
}
if (object == null) {
fieldValues.put(fieldInfo.getName(), value);
} else {
setValue(object, value);
}
}
@Override
public int getFastMatchToken() {
return fieldValueDeserilizer.getFastMatchToken();
}
万事具备,只欠东风,那么我们如何将我们的PatchParserConfig注入到解析的过程中去呢。我们使用android studio的快捷键command+左键一直跟进源码,发现JsonUtil工具类中的两个fromJson方法,最终会调用下面的两个方法
public static final <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
int featureValues, Feature... features) {
if (input == null) {
return null;
}
for (Feature featrue : features) {
featureValues = Feature.config(featureValues, featrue, true);
}
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
if (processor instanceof ExtraTypeProvider) {
parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
}
if (processor instanceof ExtraProcessor) {
parser.getExtraProcessors().add((ExtraProcessor) processor);
}
T value = (T) parser.parseObject(clazz);
parser.handleResovleTask(value);
parser.close();
return (T) value;
}
而该函数中新建的DefaultJSONParser对象内部维持了ParserConfig的一个引用,我们可以通过setter方法修改该引用为我们自己的的PatchParserConfig,于是一切都变得如此简单。
private static final ParserConfig PARSER_CONFIG = new PatchParserConfig();
private static final <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
int featureValues, Feature... features) {
if (input == null) {
return null;
}
for (Feature featrue : features) {
featureValues = Feature.config(featureValues, featrue, true);
}
DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
parser.setConfig(PARSER_CONFIG);
if (processor instanceof ExtraTypeProvider) {
parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
}
if (processor instanceof ExtraProcessor) {
parser.getExtraProcessors().add((ExtraProcessor) processor);
}
T value = (T) parser.parseObject(clazz);
parser.handleResovleTask(value);
parser.close();
return (T) value;
}
为了更加方便使用,我们在这个方法基础上再分装两个类似JSON类中的参数少的方法。
private static final <T> T parseObject(String text, Class<T> clazz) {
return (T) parseObject(text, (Type) clazz, PatchParserConfig.getGlobalInstance(),null,JSON.DEFAULT_PARSER_FEATURE, new Feature[0]);
}
private static final <T> T parseObject(String text, TypeReference<T> type, Feature... features) {
return (T) parseObject(text, type.getType(), PatchParserConfig.getGlobalInstance(), null, JSON.DEFAULT_PARSER_FEATURE, features);
}
接着改造我们原来JsonUtil类中的fromJson的两个方法,将其调用执行自己内部的静态函数parserObject,如下。
public static <T> T fromJson(String json, Class<T> clazz) {
try {
return clazz.equals(String.class) ? (T) json : parseObject(json, clazz);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static <T> T fromJson(String json, TypeReference<?> typeReference) {
try {
return (T) (typeReference.getType().equals(String.class) ? json
: parseObject(json, typeReference));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
最终,暴露给客户端的就是这两个方法。我们一个狸猫换太子,一下子就对服务器返回的空字符串进行了兼容。下面测试一下能否正常反序列化。输出结果如下
Bean{doubleParam=0.0, floatParam=2.76, extra=Extra{doubleParam=12.23, floatParam=0.0}}
异常消失了,世界都安静了。
标签:
原文地址:http://blog.csdn.net/sbsujjbcy/article/details/50414188