首页 > 编程 > C# > 正文

C#实现JSON解析器MojoUnityJson功能(简单且高效)

2020-01-24 00:21:16
字体:
来源:转载
供稿:网友

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可以很容易的嵌入到其它项目里使用。

本文主要介绍一下,超级简单又高效,并且看一眼就完全明白的解析算法,几乎可以原封不动的复制粘贴成其它语言版本的实现。

保存上下文信息

使用一个简单的结构体,用来在解析的过程中,传递一些上下文数据。

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, }

整体解析步骤

// 解析 JsonValueprivate static JsonValue ParseValue(ref Data data);// 解析 JsonObjectprivate static JsonValue ParseObject(ref Data data);// 解析 JsonArrayprivate static JsonValue ParseArray(ref Data data);// 解析 stringprivate static JsonValue ParseString(ref Data data);// 解析 numberprivate static JsonValue ParseNumber(ref Data data)

这就是全部的解析流程,在ParseValue中会根据字符判断类型,分别调用下面几个不同的解析函数。JsonValue就对应一个JSON的值,它有一个JsonType代表了这个值的类型。这是一个递归的过程,在ParseValue,ParseObject和ParseArray过程中,会递归的调用ParseValue。JSON一定是始于一个,Object或Array,当这个最顶层的值解析完毕的时候,整个JSON也就解析完成了。

解析空白字符

解析过程中,会有很多为了格式化存在的空白字符,需要剔除这些,才能获得有信息的字符,这是一个重复的过程,需要一个函数统一处理。

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 '-':   // 表示数值   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

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

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

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就可以了。

/// <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

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对象包含了所有的数值。

var jsonValue = MojoUnity.Json.Parse(jsonString);JsonValue的访问API// JsonValue 当做 stringpublic string AsString();// JsonValue 当做 floatpublic float AsFloat();// JsonValue 当做 intpublic float AsInt();// JsonValue 当做 boolpublic float AsBool();// JsonValue 当做 nullpublic float IsNull();// JsonValue 当做 Dictionarypublic Dictionary<string, JsonValue> AsObject();// JsonValue 当做 Dictionary 并根据 key 获取 value 当做JsonValue public JsonValue AsObjectGet(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 Dictionarypublic Dictionary<string, JsonValue> AsObjectGetObject(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 Listpublic List<JsonValue> AsObjectGetArray(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 stringpublic string AsObjectGetString(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 floatpublic float AsObjectGetFloat(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 intpublic int AsObjectGetInt(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 boolpublic bool AsObjectGetBool(string key);// JsonValue 当做 Dictionary 并根据 key 获取 value 当做 null public bool AsObjectGetIsNull(string key);// JsonValue 当做 Listpublic List<JsonValue> AsArray();// JsonValue 当做 List 并获取 index 的 value 当做 JsonValuepublic JsonValue AsArrayGet(int index);// JsonValue 当做 List 并获取 index 的 value 当做 Dictionarypublic Dictionary<string, JsonValue> AsArrayGetObject(int index);// JsonValue 当做 List 并获取 index 的 value 当做 Listpublic List<JsonValue> AsArrayGetArray(int index);// JsonValue 当做 List 并获取 index 的 value 当做 stringpublic string AsArrayGetString(int index);// JsonValue 当做 List 并获取 index 的 value 当做 floatpublic float AsArrayGetFloat(int index);// JsonValue 当做 List 并获取 index 的 value 当做 intpublic int AsArrayGetInt(int index);// JsonValue 当做 List 并获取 index 的 value 当做 boolpublic bool AsArrayGetBool(int index);// JsonValue 当做 List 并获取 index 的 value 当做 nullpublic bool AsArrayGetIsNull(int index);

最后

MojoUnityJson 目的就是完成简单而单一的JSON字符串解析功能,能够读取JSON的数据就是最重要的功能。在网上也了解了一些开源的C#实现的JSON库,不是功能太多太丰富,就是实现有些繁琐了,于是就手动实现了MojoUnityJson。

总结

以上所述是小编给大家介绍的C#实现JSON解析器MojoUnityJson,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对武林网网站的支持!

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表