码迷,mamicode.com
首页 > 移动开发 > 详细

Android FastJson与不规范JSON引发的血案

时间:2015-12-27 23:35:42      阅读:760      评论:0      收藏:0      [点我收藏+]

标签:

去公司实习了,没多少时间更博客了,距离上一篇博客也有一个来月了。看标题,应该可以看出,这篇文章是讲一个坑,以及如何填坑。
坑是什么?有两个坑,其一是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,但是实际上还是存在。

修复数值类型byteshortintlongfloatdoubleboolean的值为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}}

异常消失了,世界都安静了。

Android FastJson与不规范JSON引发的血案

标签:

原文地址:http://blog.csdn.net/sbsujjbcy/article/details/50414188

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!