欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-17 13:12:16
mojounityjson 是使用c#实现的json解析器 ,算法思路来自于游戏引擎mojoc的c语言实现 json.h 。借助c#的类库,可以比c的实现更加的简单和全面,...

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

整体解析步骤

// 解析 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也就解析完成了。

解析空白字符

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

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 当做 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,希望对大家有所帮助

上一篇:

下一篇: