关于fastjson和jackson将json字符串解析成实体的过程(一)

上周和别的公司接口对接,遇到了一个问题。我这边用的aes,不足16倍数后补0(java没有zeropadding,需要自己通过nopadding实现),然后加密再转base64传过去,对方也是先收到base64解密,再aes解密,转json。这时候对方报错说我传的数据有乱码,然后我这边测试了一下,传的json原数据是没乱码的,但是因为不足16倍数,需要补0,所以解密的时候可能会在json字符串后面跟上空字符(类似口这个字),我以为是这个问题导致的乱码,后面经双方查实,是因为aes偏移量的问题。于是我看了一下对面是用jackson解析的,但是我这边main方法测试用的fastjson,所以这次来研究一下两个json库json字符串转实体的区别。

首先是fastjson,上测试代码:

关于fastjson和jackson将json字符串解析成实体的过程(一)

一路点开parseObject方法

关于fastjson和jackson将json字符串解析成实体的过程(一)

点进去

关于fastjson和jackson将json字符串解析成实体的过程(一)

主要就是得到对应的反序列化器,然后进入deserialze方法

关于fastjson和jackson将json字符串解析成实体的过程(一)

这个就是根据传的是类还是参数型(比如集合)来区别反序列化,我们这里传的是class,所以点进去

关于fastjson和jackson将json字符串解析成实体的过程(一)

这里就是生成了一个对象,主要看parseObject方法:

public void parseObject(DefaultExtJSONParser parser, Object object)
    {
        Class clazz = object.getClass();
        Map setters = parser.getMapping().getSetters(clazz);
        JSONScanner lexer = parser.getLexer();
        if(lexer.token() != JSONToken.LBRACE)
            throw new JSONException((new StringBuilder()).append("syntax error, expect {, actual ").append(lexer.token()).toString());
        char ch;
        do
            do
            {
                lexer.skipWhitespace();
                ch = lexer.getCurrent();
                if(parser.isEnabled(Feature.AllowArbitraryCommas))
                    for(; ch == ','; ch = lexer.getCurrent())
                    {
                        lexer.incrementBufferPosition();
                        lexer.skipWhitespace();
                    }

                String key;
                if(ch == '"')
                {
                    key = lexer.scanSymbol(parser.getSymbolTable(), '"');
                    lexer.skipWhitespace();
                    ch = lexer.getCurrent();
                    if(ch != ':')
                        throw new JSONException((new StringBuilder()).append("expect ':' at ").append(lexer.pos()).toString());
                } else
                {
                    if(ch == '}')
                    {
                        lexer.incrementBufferPosition();
                        lexer.resetStringPosition();
                        return;
                    }
                    if(ch == '\'')
                    {
                        if(!parser.isEnabled(Feature.AllowSingleQuotes))
                            throw new JSONException("syntax error");
                        key = lexer.scanSymbol(parser.getSymbolTable(), '\'');
                        lexer.skipWhitespace();
                        ch = lexer.getCurrent();
                        if(ch != ':')
                            throw new JSONException((new StringBuilder()).append("expect ':' at ").append(lexer.pos()).toString());
                    } else
                    {
                        if(!parser.isEnabled(Feature.AllowUnQuotedFieldNames))
                            throw new JSONException("syntax error");
                        key = lexer.scanSymbolUnQuoted(parser.getSymbolTable());
                        lexer.skipWhitespace();
                        ch = lexer.getCurrent();
                        if(ch != ':')
                            throw new JSONException((new StringBuilder()).append("expect ':' at ").append(lexer.pos()).append(", actual ").append(ch).toString());
                    }
                }
                lexer.incrementBufferPosition();
                lexer.skipWhitespace();
                ch = lexer.getCurrent();
                lexer.resetStringPosition();
                Method method = (Method)setters.get(key);
                if(method == null)
                {
                    if(!parser.isIgnoreNotMatch())
                        throw new JSONException((new StringBuilder()).append("setter not found, class ").append(clazz.getName()).append(", property ").append(key).toString());
                    lexer.nextToken();
                    parser.parse();
                    if(lexer.token() == JSONToken.RBRACE)
                    {
                        lexer.nextToken();
                        return;
                    }
                } else
                {
                    try
                    {
                        Class propertyType = method.getParameterTypes()[0];
                        if(ch == '"' && propertyType == java/lang/String)
                        {
                            lexer.scanString();
                            String value = lexer.stringVal();
                            method.invoke(object, new Object[] {
                                value
                            });
                        } else
                        if((ch >= '0' && ch <= '9' || ch == '-') && (propertyType == Integer.TYPE || propertyType == java/lang/Integer))
                        {
                            lexer.scanNumber();
                            int value = lexer.intValue();
                            method.invoke(object, new Object[] {
                                Integer.valueOf(value)
                            });
                        } else
                        if((ch >= '0' && ch <= '9' || ch == '-') && (propertyType == Long.TYPE || propertyType == java/lang/Long))
                        {
                            lexer.scanNumber();
                            long value = lexer.longValue();
                            method.invoke(object, new Object[] {
                                Long.valueOf(value)
                            });
                        } else
                        {
                            lexer.nextToken();
                            Object value;
                            if(propertyType.equals(java/lang/String))
                            {
                                value = lexer.stringVal();
                                value = TypeUtils.castToString(parser.parse());
                            } else
                            if(propertyType.equals(Integer.TYPE) || propertyType.equals(java/lang/Integer))
                                value = TypeUtils.castToInt(parser.parse());
                            else
                            if(propertyType.equals(Long.TYPE) || propertyType.equals(java/lang/Long))
                                value = TypeUtils.castToLong(parser.parse());
                            else
                            if(propertyType.equals(Boolean.TYPE) || propertyType.equals(java/lang/Boolean))
                                value = TypeUtils.castToBoolean(parser.parse());
                            else
                            if(propertyType.equals(java/math/BigDecimal))
                                value = TypeUtils.castToBigDecimal(parser.parse());
                            else
                            if(propertyType.equals(java/util/Date))
                            {
                                Object parsedValue = parser.parse();
                                if(parsedValue instanceof String)
                                {
                                    String text = (String)parsedValue;
                                    JSONScanner dateLexer = new JSONScanner(text);
                                    if(dateLexer.scanISO8601DateIfMatch())
                                        value = dateLexer.getCalendar().getTime();
                                    else
                                        value = TypeUtils.castToDate(parsedValue);
                                } else
                                {
                                    value = TypeUtils.castToDate(parsedValue);
                                }
                            } else
                            if(propertyType.equals(Float.TYPE) || propertyType.equals(java/lang/Float))
                                value = TypeUtils.castToFloat(parser.parse());
                            else
                            if(propertyType.equals(Double.TYPE) || propertyType.equals(java/lang/Double))
                                value = TypeUtils.castToDouble(parser.parse());
                            else
                            if(java/util/Collection.isAssignableFrom(propertyType))
                            {
                                Type type = method.getGenericParameterTypes()[0];
                                Object argVal = parser.parseArrayWithType(type);
                                value = argVal;
                            } else
                            if(propertyType.equals(Short.TYPE) || propertyType.equals(java/lang/Short))
                                value = TypeUtils.castToShort(parser.parse());
                            else
                            if(propertyType.equals(Byte.TYPE) || propertyType.equals(java/lang/Byte))
                                value = TypeUtils.castToByte(parser.parse());
                            else
                            if(propertyType.equals(java/math/BigInteger))
                                value = TypeUtils.castToBigInteger(parser.parse());
                            else
                            if(lexer.token() == JSONToken.NULL)
                            {
                                value = null;
                                lexer.nextToken();
                            } else
                            {
                                value = parser.parseObject(propertyType);
                            }
                            method.invoke(object, new Object[] {
                                value
                            });
                            if(lexer.token() == JSONToken.RBRACE)
                            {
                                lexer.nextToken();
                                return;
                            }
                        }
                    }
                    catch(Throwable e)
                    {
                        throw new JSONException((new StringBuilder()).append("set proprety error, ").append(method.getName()).toString(), e);
                    }
                }
                lexer.skipWhitespace();
                ch = lexer.getCurrent();
                if(ch != ',')
                    break;
                lexer.incrementBufferPosition();
            } while(true);
        while(ch != '}');
        lexer.incrementBufferPosition();
        lexer.nextToken();
    }

这个就是主要的解析方法了,就是用JSONScanner去循环遍历json字符串,拿到每个字符判断是否为分隔符(冒号,引号,大括号之类),比如scanSymbol方法,然后会调用lexer.nextToken()重新设置JSONToken的值并判断当前字符是否分隔符,用于定位当前和下一步操作。然后就是调用AbstractJSONParser的parse(),代码就不贴出来了,就是根据JSONToken来生成需要的实体。最外层读到}字符基本就跳出循环了,然后bp下标加一,走nextToken方法,拿到}后一位的字符。

我在nextToken里看到了这个方法:

关于fastjson和jackson将json字符串解析成实体的过程(一)

如果我在json字符串末尾加上空字符(\0),然后报错的原因就是这个(空字符有很多种),但是你json末尾有\032的空字符其实也是可以成功转成实体的。而且这个方法导致的就是你json字符串最后一位不是}符号也可以成功转成实体,但是不能多个这种字符,比如String s = "{\"name\":\"bob\",\"age\":20}}"可以解析,但是String s = "{\"name\":\"bob\",\"age\":20}}}}"就不行,它会根据你当前的分隔符字符记住你JSONToken的值,随便截图一段吧:

关于fastjson和jackson将json字符串解析成实体的过程(一)

回到json的parseObject方法:

关于fastjson和jackson将json字符串解析成实体的过程(一)

进入最后的close方法:

关于fastjson和jackson将json字符串解析成实体的过程(一)

就是获取当前的JSONLexer,它里面有当前字符的token值,然后进入isEOF方法:

关于fastjson和jackson将json字符串解析成实体的过程(一)

关于fastjson和jackson将json字符串解析成实体的过程(一)逻辑很简单,eof就是成功了,文件结束了;error就是失败了,比如刚刚举的例子,在nextToken方法里,结尾如果是\0空字符的话就会执行lexError方法(因为源码并没对\0做处理),会把JSONToken值改为error;还有一个for循环也是刚刚举得那个结尾多个分隔符的例子,只要不是空白,就会return false抛出异常。上几个测试例子:

关于fastjson和jackson将json字符串解析成实体的过程(一)

关于fastjson和jackson将json字符串解析成实体的过程(一)

关于fastjson和jackson将json字符串解析成实体的过程(一)

关于fastjson和jackson将json字符串解析成实体的过程(一)

关于fastjson和jackson将json字符串解析成实体的过程(一)

【字符代码里并没有对这种做处理,所以也是error。

\0空字符同理:

关于fastjson和jackson将json字符串解析成实体的过程(一)

 

本次测试的fastjson版本比较老,1.0.6,有些细节没说太清楚,就根据实际问题来说的,望理解