关于fastjson和jackson将json字符串解析成实体的过程(一)
上周和别的公司接口对接,遇到了一个问题。我这边用的aes,不足16倍数后补0(java没有zeropadding,需要自己通过nopadding实现),然后加密再转base64传过去,对方也是先收到base64解密,再aes解密,转json。这时候对方报错说我传的数据有乱码,然后我这边测试了一下,传的json原数据是没乱码的,但是因为不足16倍数,需要补0,所以解密的时候可能会在json字符串后面跟上空字符(类似口这个字),我以为是这个问题导致的乱码,后面经双方查实,是因为aes偏移量的问题。于是我看了一下对面是用jackson解析的,但是我这边main方法测试用的fastjson,所以这次来研究一下两个json库json字符串转实体的区别。
首先是fastjson,上测试代码:
一路点开parseObject方法
点进去
主要就是得到对应的反序列化器,然后进入deserialze方法
这个就是根据传的是类还是参数型(比如集合)来区别反序列化,我们这里传的是class,所以点进去
这里就是生成了一个对象,主要看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里看到了这个方法:
如果我在json字符串末尾加上空字符(\0),然后报错的原因就是这个(空字符有很多种),但是你json末尾有\032的空字符其实也是可以成功转成实体的。而且这个方法导致的就是你json字符串最后一位不是}符号也可以成功转成实体,但是不能多个这种字符,比如String s = "{\"name\":\"bob\",\"age\":20}}"可以解析,但是String s = "{\"name\":\"bob\",\"age\":20}}}}"就不行,它会根据你当前的分隔符字符记住你JSONToken的值,随便截图一段吧:
回到json的parseObject方法:
进入最后的close方法:
就是获取当前的JSONLexer,它里面有当前字符的token值,然后进入isEOF方法:
逻辑很简单,eof就是成功了,文件结束了;error就是失败了,比如刚刚举的例子,在nextToken方法里,结尾如果是\0空字符的话就会执行lexError方法(因为源码并没对\0做处理),会把JSONToken值改为error;还有一个for循环也是刚刚举得那个结尾多个分隔符的例子,只要不是空白,就会return false抛出异常。上几个测试例子:
【字符代码里并没有对这种做处理,所以也是error。
\0空字符同理:
本次测试的fastjson版本比较老,1.0.6,有些细节没说太清楚,就根据实际问题来说的,望理解