MojoUnityJson 是使用C#實現的JSON解析器 ,算法思路來自于游戲引擎Mojoc的C語言實現 Json.h 。借助C#的類庫,可以比C的實現更加的簡單和全面,尤其是處理Unicode Code(\u開頭)字符的解析,C#的StringBuilder本身就支持了UnicodeCodePoint。
MojoUnityJson使用遞歸下降的解析模式,核心解析代碼只有450行(去掉空行可能只有300多行),支持標準的JSON格式。算法實現力求簡潔明了,用最直接最快速的方法達到目的,沒有復雜的概念和模式。除了解析JSON,還提供了一組方便直觀的API來訪問JSON數據,整體實現只有一個文件,僅依賴 System.Collections.Generic , System.Text , System 三個命名空間,MojoUnityJson可以很容易的嵌入到其它項目里使用。
本文主要介紹一下,超級簡單又高效,并且看一眼就完全明白的解析算法,幾乎可以原封不動的復制粘貼成其它語言版本的實現。
保存上下文信息
使用一個簡單的結構體,用來在解析的過程中,傳遞一些上下文數據。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private struct Data { // 需要解析的JSON字符串 public string json; // 當前JSON字符串解析的位置索引 public int index; // 緩存一個StringBuilder,用來摳出JSON的一段字符。 public StringBuilder sb; public Data( string json, int index) { this .json = json; this .index = index; this .sb = new StringBuilder(); } } |
抽象JSON的值
我們把JSON的值抽象成以下幾個類型:
1
2
3
4
5
6
7
8
9
|
public enum JsonType { Object, Array, String, Number, Bool, Null, } |
整體解析步驟
1
2
3
4
5
6
7
8
9
10
|
// 解析 JsonValue private static JsonValue ParseValue( ref Data data); // 解析 JsonObject private static JsonValue ParseObject( ref Data data); // 解析 JsonArray private static JsonValue ParseArray( ref Data data); // 解析 string private static JsonValue ParseString( ref Data data); // 解析 number private static JsonValue ParseNumber( ref Data data) |
這就是全部的解析流程,在ParseValue中會根據字符判斷類型,分別調用下面幾個不同的解析函數。JsonValue就對應一個JSON的值,它有一個JsonType代表了這個值的類型。這是一個遞歸的過程,在ParseValue,ParseObject和ParseArray過程中,會遞歸的調用ParseValue。JSON一定是始于一個,Object或Array,當這個最頂層的值解析完畢的時候,整個JSON也就解析完成了。
解析空白字符
解析過程中,會有很多為了格式化存在的空白字符,需要剔除這些,才能獲得有信息的字符,這是一個重復的過程,需要一個函數統一處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private static void SkipWhiteSpace( ref Data data) { while ( true ) { switch (data.json[data.index]) { case ' ' : case '\t' : case '\n' : case '\r' : data.index++; // 每次消耗一個字符,就向后推進JSON的索引 continue ; } break ; } } |
解析JsonValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
private static JsonValue ParseValue( ref Data data) { // 跳過空白字符 SkipWhiteSpace( ref data); var c = data.json[data.index]; switch (c) { case '{' : // 表示Object return ParseObject( ref data); case '[' : // 表示Array return ParseArray ( ref data); case '"' : // 表示string return ParseString( ref data); case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : case '9' : case '-' : // 表示數值 return ParseNumber( ref data); case 'f' : // 表示可能是false if ( data.json[data.index + 1] == 'a' && data.json[data.index + 2] == 'l' && data.json[data.index + 3] == 's' && data.json[data.index + 4] == 'e' ) { data.index += 5; // 表示是false return new JsonValue(JsonType.Bool, false ); } break ; case 't' : // 表示可能是true if ( data.json[data.index + 1] == 'r' && data.json[data.index + 2] == 'u' && data.json[data.index + 3] == 'e' ) { data.index += 4; // 表示是true return new JsonValue(JsonType.Bool, true ); } break ; case 'n' : // 表示可能是null if ( data.json[data.index + 1] == 'u' && data.json[data.index + 2] == 'l' && data.json[data.index + 3] == 'l' ) { data.index += 4; // 表示可能是null return new JsonValue(JsonType.Null, null ); } break ; } // 不能處理了 throw new Exception( string .Format( "Json ParseValue error on char '{0}' index in '{1}' " , c, data.index)); } |
- ParseValue是解析的主入口,代表著解析JsonValue這個抽象的JSON值,其真實的類型在解析的過程中逐漸具體化。
- 在剝離掉空白字符之后,就可以很容易的通過單個字符,就判斷出其可能的數值類型,而不需要向前或向后檢索。
- true,false,null 這幾個固定的類型,直接就處理掉了,而其它稍微復雜的類型需要使用函數來處理。
- 這里沒有使用if else,而是大量使用了case,是為了提高效率,減少判斷次數。
解析JsonObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
private static JsonValue ParseObject( ref Data data) { // Object 對應 C#的Dictionary var jsonObject = new Dictionary< string , JsonValue>(JsonObjectInitCapacity); // skip '{' data.index++; do { // 跳過空白字符 SkipWhiteSpace( ref data); if (data.json[data.index] == '}' ) { // 空的Object, "{}" break ; } DebugTool.Assert ( data.json[data.index] == '"' , "Json ParseObject error, char '{0}' should be '\"' " , data.json[data.index] ); // skip '"' data.index++; var start = data.index; // 解析Object的key值 while ( true ) { var c = data.json[data.index++]; switch (c) { case '"' : // check end '"' break ; case '\\' : // skip escaped quotes data.index++; continue ; default : continue ; } // already skip the end '"' break ; } // get object key string // 扣出key字符串 var key = data.json.Substring(start, data.index - start - 1); // 跳過空白 SkipWhiteSpace( ref data); DebugTool.Assert ( data.json[data.index] == ':' , "Json ParseObject error, after key = {0}, char '{1}' should be ':' " , key, data.json[data.index] ); // skip ':' data.index++; // set JsonObject key and value // 遞歸的調用ParseValue獲得Object的value值 jsonObject.Add(key, ParseValue( ref data)); // 跳過空白 SkipWhiteSpace( ref data); if (data.json[data.index] == ',' ) { // Object的下一對KV data.index++ ; } else { // 跳過空白 SkipWhiteSpace( ref data); DebugTool.Assert ( data.json[data.index] == '}' , "Json ParseObject error, after key = {0}, char '{1}' should be '{2}' " , key, data.json[data.index], '}' ); break ; } } while ( true ); // skip '}' and return after '}' data.index++; return new JsonValue(JsonType.Object, jsonObject); } |
JsonObject類型就簡單的對應C#的Dictionary,value是JsonValue類型。當解析完成后,value的類型就是確定的了。
JsonValue是遞歸的調用ParseValue來處理的,其類型可能是JsonType枚舉的任意類型。
解析JsonArray
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
private static JsonValue ParseArray( ref Data data) { // JsonArray 對應 List var jsonArray = new List<JsonValue>(JsonArrayInitCapacity); // skip '[' data.index++; do { // 跳過空白 SkipWhiteSpace( ref data); if (data.json[data.index] == ']' ) { // 空 "[]" break ; } // add JsonArray item // 遞歸處理List每個元素 jsonArray.Add(ParseValue( ref data)); // 跳過空白 SkipWhiteSpace( ref data); if (data.json[data.index] == ',' ) { // 解析下一個元素 data.index++; } else { // 跳過空白 SkipWhiteSpace( ref data); DebugTool.Assert ( data.json[data.index] == ']' , "Json ParseArray error, char '{0}' should be ']' " , data.json[data.index] ); break ; } } while ( true ); // skip ']' data.index++; return new JsonValue(JsonType.Array, jsonArray); } |
JsonArray類型就簡單的對應C#的List,element是JsonValue類型。當解析完成后,element的類型就是確定的了。
JsonValue是遞歸的調用ParseValue來處理的,其類型可能是JsonType枚舉的任意類型。
解析string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
private static JsonValue ParseString( ref Data data) { // skip '"' data.index++; var start = data.index; string str; // 處理字符串 while ( true ) { switch (data.json[data.index++]) { case '"' : // 字符串結束 // check end '"' if (data.sb.Length == 0) { // 沒有使用StringBuilder,直接摳出字符串 str = data.json.Substring(start, data.index - start - 1); } else { // 有特殊字符在StringBuilder str = data.sb.Append(data.json, start, data.index - start - 1).ToString(); // clear for next string // 清空字符,供下次使用 data.sb.Length = 0; } break ; case '\\' : { // check escaped char var escapedIndex = data.index; char c; // 處理各種轉義字符 switch (data.json[data.index++]) { case '"' : c = '"' ; break ; case '\'' : c = '\'' ; break ; case '\\' : c = '\\' ; break ; case '/' : c = '/' ; break ; case 'n' : c = '\n' ; break ; case 'r' : c = '\r' ; break ; case 't' : c = '\t' ; break ; case 'u' : // 計算unicode字符的碼點 c = GetUnicodeCodePoint ( data.json[data.index], data.json[data.index + 1], data.json[data.index + 2], data.json[data.index + 3] ); // skip code point data.index += 4; break ; default : // not support just add in pre string continue ; } // add pre string and escaped char // 特殊處理的字符和正常的字符,一起放入StringBuilder data.sb.Append(data.json, start, escapedIndex - start - 1).Append(c); // update pre string start index start = data.index; continue ; } default : continue ; } // already skip the end '"' break ; } return new JsonValue(JsonType.String, str); } |
處理字符串麻煩的地方在于,轉義字符需要特殊處理,都這轉義字符就會直接顯示而不能展示特殊的作用。好在StringBuilder功能非常強大,提供處理各種情況的接口。
解析Unicode字符
在JSON中,Unicode字符是以\u開頭跟隨4個碼點組成的轉義字符。碼點在StringBuilder的Append重載函數中是直接支持的。所以,我們只要把\u后面的4個字符,轉換成碼點傳遞給Append就可以了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
/// <summary> /// Get the unicode code point. /// </summary> private static char GetUnicodeCodePoint( char c1, char c2, char c3, char c4) { // 把\u后面的4個char轉換成碼點,注意這里需要是char類型,才能被Append正確處理。 // 4個char轉換為int后,映射到16進制的高位到低位,然后相加得到碼點。 return ( char ) ( UnicodeCharToInt(c1) * 0x1000 + UnicodeCharToInt(c2) * 0x100 + UnicodeCharToInt(c3) * 0x10 + UnicodeCharToInt(c4) ); } /// <summary> /// Single unicode char convert to int. /// </summary> private static int UnicodeCharToInt( char c) { // 使用switch case 減少 if else 的判斷 switch (c) { case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : case '9' : return c - '0' ; case 'a' : case 'b' : case 'c' : case 'd' : case 'e' : case 'f' : return c - 'a' + 10; case 'A' : case 'B' : case 'C' : case 'D' : case 'E' : case 'F' : return c - 'A' + 10; } throw new Exception( string .Format( "Json Unicode char '{0}' error" , c)); } |
解析number
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
private static JsonValue ParseNumber( ref Data data) { var start = data.index; // 收集數值字符 while ( true ) { switch (data.json[++data.index]) { case '0' : case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : case '8' : case '9' : case '-' : case '+' : case '.' : case 'e' : case 'E' : continue ; } break ; } // 摳出數值字符串 var strNum = data.json.Substring(start, data.index - start); float num; // 當成float處理,當然也可以用double if ( float .TryParse(strNum, out num)) { return new JsonValue(JsonType.Number, num); } else { throw new Exception( string .Format( "Json ParseNumber error, can not parse string [{0}]" , strNum)); } } |
如何使用
只有一句話,把Json字符串解析成JsonValue對象,然后JsonValue對象包含了所有的數值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
var jsonValue = MojoUnity.Json.Parse(jsonString); JsonValue的訪問API // JsonValue 當做 string public string AsString(); // JsonValue 當做 float public float AsFloat(); // JsonValue 當做 int public float AsInt(); // JsonValue 當做 bool public float AsBool(); // JsonValue 當做 null public float IsNull(); // JsonValue 當做 Dictionary public Dictionary< string , JsonValue> AsObject(); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做JsonValue public JsonValue AsObjectGet( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 Dictionary public Dictionary< string , JsonValue> AsObjectGetObject( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 List public List<JsonValue> AsObjectGetArray( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 string public string AsObjectGetString( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 float public float AsObjectGetFloat( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 int public int AsObjectGetInt( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 bool public bool AsObjectGetBool( string key); // JsonValue 當做 Dictionary 并根據 key 獲取 value 當做 null public bool AsObjectGetIsNull( string key); // JsonValue 當做 List public List<JsonValue> AsArray(); // JsonValue 當做 List 并獲取 index 的 value 當做 JsonValue public JsonValue AsArrayGet( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 Dictionary public Dictionary< string , JsonValue> AsArrayGetObject( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 List public List<JsonValue> AsArrayGetArray( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 string public string AsArrayGetString( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 float public float AsArrayGetFloat( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 int public int AsArrayGetInt( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 bool public bool AsArrayGetBool( int index); // JsonValue 當做 List 并獲取 index 的 value 當做 null public bool AsArrayGetIsNull( int index); |
最后
MojoUnityJson 目的就是完成簡單而單一的JSON字符串解析功能,能夠讀取JSON的數據就是最重要的功能。在網上也了解了一些開源的C#實現的JSON庫,不是功能太多太豐富,就是實現有些繁瑣了,于是就手動實現了MojoUnityJson。
總結
以上所述是小編給大家介紹的C#實現JSON解析器MojoUnityJson,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:https://www.jianshu.com/p/76a441d1ac44