C#實現(xiàn)JSON解析器MojoUnityJson功能(簡單且高效)
MojoUnityJson 是使用C#實現(xiàn)的JSON解析器 ,算法思路來自于游戲引擎Mojoc的C語言實現(xiàn) Json.h 。借助C#的類庫,可以比C的實現(xiàn)更加的簡單和全面,尤其是處理Unicode Code(\u開頭)字符的解析,C#的StringBuilder本身就支持了UnicodeCodePoint。
MojoUnityJson使用遞歸下降的解析模式,核心解析代碼只有450行(去掉空行可能只有300多行),支持標準的JSON格式。算法實現(xiàn)力求簡潔明了,用最直接最快速的方法達到目的,沒有復雜的概念和模式。除了解析JSON,還提供了一組方便直觀的API來訪問JSON數(shù)據(jù),整體實現(xiàn)只有一個文件,僅依賴 System.Collections.Generic , System.Text , System 三個命名空間,MojoUnityJson可以很容易的嵌入到其它項目里使用。
本文主要介紹一下,超級簡單又高效,并且看一眼就完全明白的解析算法,幾乎可以原封不動的復制粘貼成其它語言版本的實現(xiàn)。
保存上下文信息
使用一個簡單的結(jié)構(gòu)體,用來在解析的過程中,傳遞一些上下文數(shù)據(jù)。
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的值抽象成以下幾個類型:
public enum JsonType
{
Object,
Array,
String,
Number,
Bool,
Null,
}
整體解析步驟
// 解析 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中會根據(jù)字符判斷類型,分別調(diào)用下面幾個不同的解析函數(shù)。JsonValue就對應一個JSON的值,它有一個JsonType代表了這個值的類型。這是一個遞歸的過程,在ParseValue,ParseObject和ParseArray過程中,會遞歸的調(diào)用ParseValue。JSON一定是始于一個,Object或Array,當這個最頂層的值解析完畢的時候,整個JSON也就解析完成了。
解析空白字符
解析過程中,會有很多為了格式化存在的空白字符,需要剔除這些,才能獲得有信息的字符,這是一個重復的過程,需要一個函數(shù)統(tǒng)一處理。
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
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 '-':
// 表示數(shù)值
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值,其真實的類型在解析的過程中逐漸具體化。
- 在剝離掉空白字符之后,就可以很容易的通過單個字符,就判斷出其可能的數(shù)值類型,而不需要向前或向后檢索。
- true,false,null 這幾個固定的類型,直接就處理掉了,而其它稍微復雜的類型需要使用函數(shù)來處理。
- 這里沒有使用if else,而是大量使用了case,是為了提高效率,減少判斷次數(shù)。
解析JsonObject
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
// 遞歸的調(diào)用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是遞歸的調(diào)用ParseValue來處理的,其類型可能是JsonType枚舉的任意類型。
解析JsonArray
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是遞歸的調(diào)用ParseValue來處理的,其類型可能是JsonType枚舉的任意類型。
解析string
private static JsonValue ParseString(ref Data data)
{
// skip '"'
data.index++;
var start = data.index;
string str;
// 處理字符串
while (true)
{
switch (data.json[data.index++])
{
case '"': // 字符串結(jié)束
// 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;
// 處理各種轉(zhuǎn)義字符
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);
}
處理字符串麻煩的地方在于,轉(zhuǎn)義字符需要特殊處理,都這轉(zhuǎn)義字符就會直接顯示而不能展示特殊的作用。好在StringBuilder功能非常強大,提供處理各種情況的接口。
解析Unicode字符
在JSON中,Unicode字符是以\u開頭跟隨4個碼點組成的轉(zhuǎn)義字符。碼點在StringBuilder的Append重載函數(shù)中是直接支持的。所以,我們只要把\u后面的4個字符,轉(zhuǎn)換成碼點傳遞給Append就可以了。
/// <summary>
/// Get the unicode code point.
/// </summary>
private static char GetUnicodeCodePoint(char c1, char c2, char c3, char c4)
{
// 把\u后面的4個char轉(zhuǎn)換成碼點,注意這里需要是char類型,才能被Append正確處理。
// 4個char轉(zhuǎn)換為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
private static JsonValue ParseNumber(ref Data data)
{
var start = data.index;
// 收集數(shù)值字符
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;
}
// 摳出數(shù)值字符串
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對象包含了所有的數(shù)值。
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 并根據(jù) key 獲取 value 當做JsonValue public JsonValue AsObjectGet(string key); // JsonValue 當做 Dictionary 并根據(jù) key 獲取 value 當做 Dictionary public Dictionary<string, JsonValue> AsObjectGetObject(string key); // JsonValue 當做 Dictionary 并根據(jù) key 獲取 value 當做 List public List<JsonValue> AsObjectGetArray(string key); // JsonValue 當做 Dictionary 并根據(jù) key 獲取 value 當做 string public string AsObjectGetString(string key); // JsonValue 當做 Dictionary 并根據(jù) key 獲取 value 當做 float public float AsObjectGetFloat(string key); // JsonValue 當做 Dictionary 并根據(jù) key 獲取 value 當做 int public int AsObjectGetInt(string key); // JsonValue 當做 Dictionary 并根據(jù) key 獲取 value 當做 bool public bool AsObjectGetBool(string key); // JsonValue 當做 Dictionary 并根據(jù) 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的數(shù)據(jù)就是最重要的功能。在網(wǎng)上也了解了一些開源的C#實現(xiàn)的JSON庫,不是功能太多太豐富,就是實現(xiàn)有些繁瑣了,于是就手動實現(xiàn)了MojoUnityJson。
總結(jié)
以上所述是小編給大家介紹的C#實現(xiàn)JSON解析器MojoUnityJson,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對我們網(wǎng)站的支持!
上一篇:C#如何通過匿名類直接使用訪問JSON數(shù)據(jù)詳解
欄 目:C#教程
本文標題:C#實現(xiàn)JSON解析器MojoUnityJson功能(簡單且高效)
本文地址:http://www.jygsgssxh.com/a1/C_jiaocheng/5281.html
您可能感興趣的文章
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻播放器左下角滾動新聞效果的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已安裝軟件變化的方法
- 01-10C#實現(xiàn)多線程下載文件的方法
- 01-10C#實現(xiàn)Winform中打開網(wǎng)頁頁面的方法
- 01-10C#實現(xiàn)遠程關(guān)閉計算機或重啟計算機的方法
- 01-10C#自定義簽名章實現(xiàn)方法
- 01-10C#文件斷點續(xù)傳實現(xiàn)方法
- 01-10winform實現(xiàn)創(chuàng)建最前端窗體的方法


閱讀排行
本欄相關(guān)
- 01-10C#通過反射獲取當前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻播放器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已
隨機閱讀
- 01-11ajax實現(xiàn)頁面的局部加載
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 04-02jquery與jsp,用jquery
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10C#中split用法實例總結(jié)
- 01-10delphi制作wav文件的方法


