还是从part0那个json串入手,看看Fastjson框架是怎么把他拆出来的。
首先,Fastjson是用JSONObject和JSONArray这两个类分别描述json对象和json数组的。那么先把这两个类拆开来看看。
通过阅读json文档得知,一个object由若干个(可以是0个)键值对组成,其中键是字符串(string),值可以是string,number,object,array,boolean,null这几种情况。那么这种数据结构在Java中对应了什么呢?
没错就是Map啊!~~
所以JSONObject封装了一个private final Map
,那么这个map是什么map呢?这取决于构造器了,默认的构造器是:
public JSONObject(){
this(DEFAULT_INITIAL_CAPACITY, false);
}
第一个参数代表初始容量,程序设定为16,第二个参数代表是否有序,默认为false。
如果有序,则用LinkedHashMap实现,否则用HashMap实现。
既然这样,那么JSON对象的key就不可以是重复的~~之前拆的cJSON是可以重复的,个人感觉就不对了。因为json文档中说
An object is an unordered set of name/value pairs.
既然是set,就不能重复的吧。。。
这个类的方法都比较简单,大多是HashMap的封装,get和set这种的。首先是各种根据key取对象的方法,有取Object的,取Array的,取JavaBean的等等。
public JSONObject getJSONObject(String key) {
Object value = map.get(key);
if (value instanceof JSONObject) {
return (JSONObject) value;
}
if (value instanceof String) {
return JSON.parseObject((String) value);
}
return (JSONObject) toJSON(value);
}
取对象的逻辑很简单,就是如果他是对应的对象,则直接取出来,如果是字符串,则parse成对象,否则转换成JSONObject(如果是JavaBean)。
此外,该框架还支持直接取日期、取时间戳、取BigInteger等封装好的方法,在底层均是调用了com.alibaba.fastjson.util包里的工具方法,在此先不分析。
然后添加元素方面呢,有一个比较有意思的fluentPut:
public JSONObject fluentPut(String key, Object value) {
map.put(key, value);
return this;
}
因为他返回的是this,所以就可以在一行代码里连续加入多个元素:(记得以前看php的时候看到连续调用觉得特别方便,其实实现原理都是一样的。)
myObj.fluentPut("key1",value1).fluentPut("key2",value2);
JSONArray和JSONObject差不多,就是底层结构换成了ArrayList。
前面说过,这个框架支持将json中的value直接转换成各种数字类、高精度、日期、时间戳等数据结构,都是在TypeUtils包中的castToxxx方法实现的。
castToString、Byte、Char、Short、BigInteger、BigDecimal、Float、Double等都比较简单,主要就是特殊处理了value为”“,”NULL”,”null”三种情况,如果处理不了,就抛出JSONException异常。
关键是Date,可见该框架支持了很多很多日期类型,首先调用了词法分析器JSONScanner,先检测是否符合ISO 8601标准,再检测是否是”yyyy-MM-dd HH:mm:ss”,”yyyy-MM-dd”,”yyyy-MM-dd HH:mm:ss.SSS”等形式。
else if (strVal.length() == 10) {
format = "yyyy-MM-dd";
} else if (strVal.length() == "yyyy-MM-dd HH:mm:ss".length()) {
format = "yyyy-MM-dd HH:mm:ss";
}
顺便说下,这两句话风格统一下好不好。。。。
最后如果都不是,则看是不是时间戳,如果是时间戳则转换为对应的日期。
* JavaBean
还有一种特殊情况,就是取指定类,这个就比较麻烦了,如果是一些简单类如Integer,Float等直接调用相关方法即可,或者value存的就是你指定的类,都比较简单。
最复杂的情况就是,JSONObject里面存的是一个Map,而要取出的是这个Map对应的Bean。这个逻辑在方法castToJavaBean里实现。
反序列化分为两部分,一是转换为JSON对象,二是转换为JavaBean。
了解了JSON对象在框架内存储的结构之后,我们从输入字符串开始,对解析流程进行分析。
public static Object parse(String text) {
return parse(text, DEFAULT_PARSER_FEATURE);
}
这是第一层接口,然后再调用重载函数:
public static Object parse(String text, int features) {
if (text == null) {
return null;
}
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
Object value = parser.parse();
parser.handleResovleTask(value);
parser.close();
return value;
}
我们发现,字符串的处理工作是在DefaultJSONParser类进行的,程序将text字符串传入,并使用默认配置和默认特征。(配置项都是什么东西,先不管了,估计也看不懂。。。)
public DefaultJSONParser(final Object input, final JSONLexer lexer, final ParserConfig config){
this.lexer = lexer;
this.input = input;
this.config = config;
this.symbolTable = config.symbolTable;
int ch = lexer.getCurrent();
if (ch == '{') {
lexer.next();
((JSONLexerBase) lexer).token = JSONToken.LBRACE;
} else if (ch == '[') {
lexer.next();
((JSONLexerBase) lexer).token = JSONToken.LBRACKET;
} else {
lexer.nextToken(); // prime the pump
}
}
观察这个东西的构造器发现,解析字符串的工作需要接受三个参数,分别是输入的字符串,词法分析器lexer,和配置项Config。如果不传参数,均使用默认设置。
词法分析器的实现是com.alibaba.fastjson.parser包中的JSONScanner类,这个类有1300多行代码,是该模块的核心部分。
有个需要注意的地方,就是程序在构造的时候就先判断了第一个符号,并让lexer转换了状态。这应该是一种提高性能的办法吧~~~
接下来执行Object value = parser.parse();
开始分析字符串。
程序在JSONToken类中定义了以下符号,供词法分析器使用(分别代表词法分析器的状态):
public final static int ERROR = 1;
public final static int LITERAL_INT = 2;
public final static int LITERAL_FLOAT = 3;
public final static int LITERAL_STRING = 4;
public final static int LITERAL_ISO8601_DATE = 5;
public final static int TRUE = 6;
public final static int FALSE = 7;
public final static int NULL = 8;
public final static int NEW = 9;
public final static int LPAREN = 10; // ("("),
public final static int RPAREN = 11; // (")"),
public final static int LBRACE = 12; // ("{"),
public final static int RBRACE = 13; // ("}"),
public final static int LBRACKET = 14; // ("["),
public final static int RBRACKET = 15; // ("]"),
public final static int COMMA = 16; // (","),
public final static int COLON = 17; // (":"),
public final static int IDENTIFIER = 18;
public final static int FIELD_NAME = 19;
public final static int EOF = 20;
public final static int SET = 21;
public final static int TREE_SET = 22;
public final static int UNDEFINED = 23; // undefined
以下是parse的源码:
public Object parse(Object fieldName) {
final JSONLexer lexer = this.lexer;
switch (lexer.token()) {
case SET:
lexer.nextToken();
HashSet<Object> set = new HashSet<Object>();
parseArray(set, fieldName);
return set;
case TREE_SET:
lexer.nextToken();
TreeSet<Object> treeSet = new TreeSet<Object>();
parseArray(treeSet, fieldName);
return treeSet;
case LBRACKET:
JSONArray array = new JSONArray();
parseArray(array, fieldName);
if (lexer.isEnabled(Feature.UseObjectArray)) {
return array.toArray();
}
return array;
case LBRACE:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
return parseObject(object, fieldName);
case LITERAL_INT:
Number intValue = lexer.integerValue();
lexer.nextToken();
return intValue;
case LITERAL_FLOAT:
Object value = lexer.decimalValue(lexer.isEnabled(Feature.UseBigDecimal));
lexer.nextToken();
return value;
case LITERAL_STRING:
String stringLiteral = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
try {
if (iso8601Lexer.scanISO8601DateIfMatch()) {
return iso8601Lexer.getCalendar().getTime();
}
} finally {
iso8601Lexer.close();
}
}
return stringLiteral;
case NULL:
lexer.nextToken();
return null;
case UNDEFINED:
lexer.nextToken();
return null;
case TRUE:
lexer.nextToken();
return Boolean.TRUE;
case FALSE:
lexer.nextToken();
return Boolean.FALSE;
case NEW:
lexer.nextToken(JSONToken.IDENTIFIER);
if (lexer.token() != JSONToken.IDENTIFIER) {
throw new JSONException("syntax error");
}
lexer.nextToken(JSONToken.LPAREN);
accept(JSONToken.LPAREN);
long time = ((Number) lexer.integerValue()).longValue();
accept(JSONToken.LITERAL_INT);
accept(JSONToken.RPAREN);
return new Date(time);
case EOF:
if (lexer.isBlankInput()) {
return null;
}
throw new JSONException("unterminated json string, " + lexer.info());
case ERROR:
default:
throw new JSONException("syntax error, " + lexer.info());
}
}
观察parse函数的源码不难发现,其实核心算法就是一个自动机转换,那么接下来分析各个状态。
SET、TREE_SET:不明
LBRACKET:说明词法分析器遇到了’[‘,这表明是一个数组的开始,所以new了一个JSONArray对象,再调用parseArray。
parseArray函数首先让词法分析器沿着字符串寻找,找到’LITERAL_STRING’状态,大概就是遇到了有效字符(非空、换行之类),然后从LITERAL_STRING开始读,读完第一个元素:
case JSONToken.LITERAL_STRING:
if (ch == '"') {
pos = bp;
scanString();
return;
}
if (ch >= '0' && ch <= '9') {
pos = bp;
scanNumber();
return;
}
if (ch == '[') {
token = JSONToken.LBRACKET;
next();
return;
}
if (ch == '{') {
token = JSONToken.LBRACE;
next();
return;
}
break;
这时候可能会遇到字符串、数字、[、{、分别对应不同的状态,如果是字符串或数字,则调用scanString或scanNumber将对应字符存进一个缓冲区,并把状态切换到LITERAL_INT或者LITERAL_FLOAT或者LITERAL_STRING.
switch (lexer.token()) {
case LITERAL_INT:
value = lexer.integerValue();
lexer.nextToken(JSONToken.COMMA);
break;
case LITERAL_FLOAT:
if (lexer.isEnabled(Feature.UseBigDecimal)) {
value = lexer.decimalValue(true);
} else {
value = lexer.decimalValue(false);
}
lexer.nextToken(JSONToken.COMMA);
break;
case LITERAL_STRING:
String stringLiteral = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
if (iso8601Lexer.scanISO8601DateIfMatch()) {
value = iso8601Lexer.getCalendar().getTime();
} else {
value = stringLiteral;
}
iso8601Lexer.close();
} else {
value = stringLiteral;
}
break;
接下来进入这一段,获取字符串/数字的值,其中用到了三个配置项:
(1)Feature.AllowArbitraryCommas:允许多重逗号,如果设为true,则遇到多个逗号会直接跳过;
(2)Feature.UseBigDecimal:这个设置为true则用BigDecimal类来装载数字,否则用的是double;
(3)Feature.AllowISO8601DateFormat:这个设置为true则遇到字符串符合ISO8601格式的日期时,会直接转换成日期类。
case LBRACE:
JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField));
value = parseObject(object, i);
break;
case LBRACKET:
Collection items = new JSONArray();
parseArray(items, i);
if (lexer.isEnabled(Feature.UseObjectArray)) {
value = items.toArray();
} else {
value = items;
}
break;
若是遇到左括号,则递归调用下去,继续解析对象或数组。
接下来调用lexer.nextToken(JSONToken.COMMA);
,寻找一个逗号。找到逗号后,执行这一段:
array.add(value); //把value加入JSONarray中
checkListResolve(array);//不明
if (lexer.token() == JSONToken.COMMA) { //再找下一个LITERAL_STRING
lexer.nextToken(JSONToken.LITERAL_STRING);
continue;
}
接着加入下一个元素,直到遇到右方括号。
case RBRACKET:
lexer.nextToken(JSONToken.COMMA);
return;
可以看到程序return出来了。
接着回到上面,如果遇到左大括号,首先new了一个JSONObject类,使用Feature.OrderedField配置项来控制是否有序,也就是用LinkedHashMap实现还是用HashMap实现,接下来我们就进入了parseObject方法。
这两个方法八成不是一个人写的,思路完全不一样了~~
既然是Object,那就是带key的, 首先要找key,发现这个框架是支持用数字做key的~这个parseObject函数真的是非常复杂,有近400行长,我实在是看不明白了。。。。
剩下的几个状态就是该json只有一个值,比如只有一个数等等的情况。直接返回就好了~~
今天就琢磨到这,下次争取深入探讨fastjson对字符串的处理。