1. 程式人生 > >如何編寫一個JSON解析器

如何編寫一個JSON解析器

編寫一個JSON解析器實際上就是一個函式,它的輸入是一個表示JSON的字串,輸出是結構化的對應到語言本身的資料結構。

和XML相比,JSON本身結構非常簡單,並且僅有幾種資料型別,以Java為例,對應的資料結構是:

  • "string":Java的 String ;
  • number:Java的 Long 或 Double ;
  • true/false:Java的 Boolean ;
  • null:Java的 null ;
  • [array]:Java的 List<Object> 或 Object[] ;
  • {"key":"value"}:Java的 Map<String, Object> 。

解析JSON和解析XML類似,最終都是解析為記憶體的一個物件。出於效率考慮,使用流的方式幾乎是唯一選擇,也就是解析器只從頭掃描一遍JSON字串,就完整地解析出對應的資料結構。

本質上解析器就是一個狀態機,只要按照JSON定義的格式(參考 http://www.json.org ,正確實現狀態轉移即可。但是為了簡化程式碼,我們也沒必要完整地實現一個字元一個字元的狀態轉移。

解析器的輸入應該是一個字元流,所以,第一步是獲得 Reader ,以便能不斷地讀入下一個字元。

在解析的過程中,我們經常要根據下一個字元來決定狀態跳轉,此時又涉及到回退的問題,就是某些時候不能用 next() 取下一個字元,而是用 peek() 取下一個字元,但字元流的指標不移動。所以, Reader 介面不能滿足這個需求,應當進一步封裝一個 CharReader ,它可以實現:

  • char next():讀取下一個字元,移動 Reader
     指標;
  • char peek():讀取下一個字元,不移動 Reader 指標;
  • String next(int size):讀取指定的N個字元並移動指標;
  • boolean hasMore():判斷流是否結束。

JSON解析比其他文字解析要簡單的地方在於,任何JSON資料型別,只需要根據下一個字元即可確定,仔細總結可以發現,如果 peek() 返回的字元是某個字元,就可以期望讀取的資料型別:

  • {:期待一個JSON object;
  • ::期待一個JSON object的value;
  • ,:期待一個JSON object的下一組key-value,或者一個JSON array的下一個元素;
  • [:期待一個JSON array;
  • t:期待一個true;
  • f:期待一個false;
  • n:期待一個null;
  • ":期待一個string;
  • 0~9:期待一個number。

但是單個字元要匹配的狀態太多了,需要進一步把字元流變為 Token ,可以總結出如下幾種 Token :

  • END_DOCUMENT:JSON文件結束;
  • BEGIN_OBJECT:開始一個JSON object;
  • END_OBJECT:結束一個JSON object;
  • BEGIN_ARRAY:開始一個JSON array;
  • END_ARRAY:結束一個JSON array;
  • SEP_COLON:讀取一個冒號;
  • SEP_COMMA:讀取一個逗號;
  • STRING:一個String;
  • BOOLEAN:一個true或false;
  • NUMBER:一個number;
  • NULL:一個null。

然後,將 CharReader 進一步封裝為 TokenReader ,提供以下介面:

  • Token readNextToken():讀取下一個Token;
  • boolean readBoolean():讀取一個boolean;
  • Number readNumber():讀取一個number;
  • String readString():讀取一個string;
  • void readNull():讀取一個null。

由於JSON的Object和Array可以巢狀,在讀取過程中,使用一個棧來儲存Object和Array是必須的。每當我們讀到一個 BEGIN_OBJECT 時,就建立一個 Map 並壓棧;每當讀到一個 BEGIN_ARRAY 時,就建立一個 List 並壓棧;每當讀到一個 END_OBJECT 和 END_ARRAY 時,就彈出棧頂元素,並根據新的棧頂元素判斷是否壓棧。此外,讀到Object的Key也必須壓棧,讀到後面的Value後將Key-Value壓入棧頂的Map。

如果讀到 END_DOCUMENT 時,棧恰好只剩下一個元素,則讀取正確,將該元素返回,讀取結束。如果棧剩下不止一個元素,則JSON文件格式不正確。

最後, JsonReader 的核心解析程式碼 parse() 就是負責從 TokenReader 中不斷讀取Token,根據當前狀態操作,然後設定下一個Token期望的狀態,如果與期望狀態不符,則JSON的格式無效。起始狀態被設定為 STATUS_EXPECT_SINGLE_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY ,即期望讀取到單個value、 { 或 [ 。迴圈的退出點是讀取到 END_DOCUMENT 時。

public class JsonReader {
TokenReader reader;
public Object parse() {
Stack stack = new Stack();
int status = STATUS_EXPECT_SINGLE_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY;
for (;;) {
Token currentToken = reader.readNextToken();
switch (currentToken) {
case BOOLEAN:
if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
// single boolean:
Boolean bool = reader.readBoolean();
stack.push(StackValue.newJsonSingle(bool));
status = STATUS_EXPECT_END_DOCUMENT;
continue;
}
if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
Boolean bool = reader.readBoolean();
String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, bool);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
continue;
}
if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
Boolean bool = reader.readBoolean();
stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(bool);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
continue;
}
throw new JsonParseException("Unexpected boolean.", reader.reader.readed);
case NULL:
if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
// single null:
reader.readNull();
stack.push(StackValue.newJsonSingle(null));
status = STATUS_EXPECT_END_DOCUMENT;
continue;
}
if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
reader.readNull();
String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, null);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
continue;
}
if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
reader.readNull();
stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(null);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
continue;
}
throw new JsonParseException("Unexpected null.", reader.reader.readed);
case NUMBER:
if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
// single number:
Number number = reader.readNumber();
stack.push(StackValue.newJsonSingle(number));
status = STATUS_EXPECT_END_DOCUMENT;
continue;
}
if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
Number number = reader.readNumber();
String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, number);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
continue;
}
if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
Number number = reader.readNumber();
stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(number);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
continue;
}
throw new JsonParseException("Unexpected number.", reader.reader.readed);
case STRING:
if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
// single string:
String str = reader.readString();
stack.push(StackValue.newJsonSingle(str));
status = STATUS_EXPECT_END_DOCUMENT;
continue;
}
if (hasStatus(STATUS_EXPECT_OBJECT_KEY)) {
String str = reader.readString();
stack.push(StackValue.newJsonObjectKey(str));
status = STATUS_EXPECT_COLON;
continue;
}
if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
String str = reader.readString();
String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, str);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
continue;
}
if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
String str = reader.readString();
stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(str);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
continue;
}
throw new JsonParseException("Unexpected char \'\"\'.", reader.reader.readed);
case SEP_COLON: // :
if (status == STATUS_EXPECT_COLON) {
status = STATUS_EXPECT_OBJECT_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY;
continue;
}
throw new JsonParseException("Unexpected char \':\'.", reader.reader.readed);
case SEP_COMMA: // ,
if (hasStatus(STATUS_EXPECT_COMMA)) {
if (hasStatus(STATUS_EXPECT_END_OBJECT)) {
status = STATUS_EXPECT_OBJECT_KEY;
continue;
}
if (hasStatus(STATUS_EXPECT_END_ARRAY)) {
status = STATUS_EXPECT_ARRAY_VALUE | STATUS_EXPECT_BEGIN_ARRAY | STATUS_EXPECT_BEGIN_OBJECT;
continue;
}
}
throw new JsonParseException("Unexpected char \',\'.", reader.reader.readed);
case END_ARRAY:
if (hasStatus(STATUS_EXPECT_END_ARRAY)) {
StackValue array = stack.pop(StackValue.TYPE_ARRAY);
if (stack.isEmpty()) {
stack.push(array);
status = STATUS_EXPECT_END_DOCUMENT;
continue;
}
int type = stack.getTopValueType();
if (type == StackValue.TYPE_OBJECT_KEY) {
// key: [ CURRENT ] ,}
String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, array.value);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
continue;
}
if (type == StackValue.TYPE_ARRAY) {
// xx, xx, [CURRENT] ,]
stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(array.value);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
continue;
}
}
throw new JsonParseException("Unexpected char: \']\'.", reader.reader.readed);
case END_OBJECT:
if (hasStatus(STATUS_EXPECT_END_OBJECT)) {
StackValue object = stack.pop(StackValue.TYPE_OBJECT);
if (stack.isEmpty()) {
// root object:
stack.push(object);
status = STATUS_EXPECT_END_DOCUMENT;
continue;
}
int type = stack.getTopValueType();
if (type == StackValue.TYPE_OBJECT_KEY) {
String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, object.value);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
continue;
}
if (type == StackValue.TYPE_ARRAY) {
stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(object.value);
status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
continue;
}
}
throw new JsonParseException("Unexpected char: \'}\'.", reader.reader.readed);
case END_DOCUMENT:
if (hasStatus(STATUS_EXPECT_END_DOCUMENT)) {
StackValue v = stack.pop();
if (stack.isEmpty()) {
return v.value;
}
}
throw new JsonParseException("Unexpected EOF.", reader.reader.readed);
case BEGIN_ARRAY:
if (hasStatus(STATUS_EXPECT_BEGIN_ARRAY)) {
stack.push(StackValue.newJsonArray(this.jsonArrayFactory.createJsonArray()));
status = STATUS_EXPECT_ARRAY_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY | STATUS_EXPECT_END_ARRAY;
continue;
}
throw new JsonParseException("Unexpected char: \'[\'.", reader.reader.readed);
case BEGIN_OBJECT:
if (hasStatus(STATUS_EXPECT_BEGIN_OBJECT)) {
stack.push(StackValue.newJsonObject(this.jsonObjectFactory.createJsonObject()));
status = STATUS_EXPECT_OBJECT_KEY | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_END_OBJECT;
continue;
}
throw new JsonParseException("Unexpected char: \'{\'.", reader.reader.readed);
}
}
}
}