溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C#如何實現(xiàn)JSON解析器MojoUnityJson功能

發(fā)布時間:2021-05-17 10:33:21 來源:億速云 閱讀:227 作者:小新 欄目:編程語言

這篇文章主要介紹了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多行),支持標(biāo)準(zhǔn)的JSON格式。算法實現(xiàn)力求簡潔明了,用最直接最快速的方法達(dá)到目的,沒有復(fù)雜的概念和模式。除了解析JSON,還提供了一組方便直觀的API來訪問JSON數(shù)據(jù),整體實現(xiàn)只有一個文件,僅依賴 System.Collections.Generic , System.Text System 三個命名空間,MojoUnityJson可以很容易的嵌入到其它項目里使用。

本文主要介紹一下,超級簡單又高效,并且看一眼就完全明白的解析算法,幾乎可以原封不動的復(fù)制粘貼成其它語言版本的實現(xiàn)。

保存上下文信息

使用一個簡單的結(jié)構(gòu)體,用來在解析的過程中,傳遞一些上下文數(shù)據(jù)。

private struct Data
{
 // 需要解析的JSON字符串
 public string  json;
 // 當(dāng)前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就對應(yīng)一個JSON的值,它有一個JsonType代表了這個值的類型。這是一個遞歸的過程,在ParseValue,ParseObject和ParseArray過程中,會遞歸的調(diào)用ParseValue。JSON一定是始于一個,Object或Array,當(dāng)這個最頂層的值解析完畢的時候,整個JSON也就解析完成了。

解析空白字符

解析過程中,會有很多為了格式化存在的空白字符,需要剔除這些,才能獲得有信息的字符,這是一個重復(fù)的過程,需要一個函數(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++; // 每次消耗一個字符,就向后推進(jìn)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 這幾個固定的類型,直接就處理掉了,而其它稍微復(fù)雜的類型需要使用函數(shù)來處理。

  • 這里沒有使用if else,而是大量使用了case,是為了提高效率,減少判斷次數(shù)。

解析JsonObject

private static JsonValue ParseObject(ref Data data)
{
 // Object 對應(yīng) 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類型就簡單的對應(yīng)C#的Dictionary,value是JsonValue類型。當(dāng)解析完成后,value的類型就是確定的了。

JsonValue是遞歸的調(diào)用ParseValue來處理的,其類型可能是JsonType枚舉的任意類型。

解析JsonArray

private static JsonValue ParseArray(ref Data data)
{
 // JsonArray 對應(yīng) 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類型就簡單的對應(yīng)C#的List,element是JsonValue類型。當(dāng)解析完成后,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功能非常強(qiáng)大,提供處理各種情況的接口。

解析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進(jìn)制的高位到低位,然后相加得到碼點。
 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;
 // 當(dāng)成float處理,當(dāng)然也可以用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 當(dāng)做 string
public string AsString();
// JsonValue 當(dāng)做 float
public float AsFloat();
// JsonValue 當(dāng)做 int
public float AsInt();
// JsonValue 當(dāng)做 bool
public float AsBool();
// JsonValue 當(dāng)做 null
public float IsNull();
// JsonValue 當(dāng)做 Dictionary
public Dictionary<string, JsonValue> AsObject();
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做JsonValue 
public JsonValue AsObjectGet(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 Dictionary
public Dictionary<string, JsonValue> AsObjectGetObject(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 List
public List<JsonValue> AsObjectGetArray(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 string
public string AsObjectGetString(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 float
public float AsObjectGetFloat(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 int
public int AsObjectGetInt(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 bool
public bool AsObjectGetBool(string key);
// JsonValue 當(dāng)做 Dictionary 并根據(jù) key 獲取 value 當(dāng)做 null 
public bool AsObjectGetIsNull(string key);
// JsonValue 當(dāng)做 List
public List<JsonValue> AsArray();
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 JsonValue
public JsonValue AsArrayGet(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 Dictionary
public Dictionary<string, JsonValue> AsArrayGetObject(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 List
public List<JsonValue> AsArrayGetArray(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 string
public string AsArrayGetString(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 float
public float AsArrayGetFloat(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 int
public int AsArrayGetInt(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 bool
public bool AsArrayGetBool(int index);
// JsonValue 當(dāng)做 List 并獲取 index 的 value 當(dāng)做 null
public bool AsArrayGetIsNull(int index);

最后

MojoUnityJson 目的就是完成簡單而單一的JSON字符串解析功能,能夠讀取JSON的數(shù)據(jù)就是最重要的功能。在網(wǎng)上也了解了一些開源的C#實現(xiàn)的JSON庫,不是功能太多太豐富,就是實現(xiàn)有些繁瑣了,于是就手動實現(xiàn)了MojoUnityJson。

C#是什么

C#是一個簡單、通用、面向?qū)ο蟮木幊陶Z言,它由微軟Microsoft開發(fā),繼承了C和C++強(qiáng)大功能,并且去掉了一些它們的復(fù)雜特性,C#綜合了VB簡單的可視化操作和C++的高運(yùn)行效率,以其強(qiáng)大的操作能力、優(yōu)雅的語法風(fēng)格、創(chuàng)新的語言特性和便捷的面向組件編程從而成為.NET開發(fā)的首選語言,但它不適用于編寫時間急迫或性能非常高的代碼,因為C#缺乏性能極高的應(yīng)用程序所需要的關(guān)鍵功能。

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“C#如何實現(xiàn)JSON解析器MojoUnityJson功能”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI