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

作者:scottcgi 时间:2023-12-02 16:44:50 

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网站的支持!

来源:https://www.jianshu.com/p/76a441d1ac44

标签:c#,json,解析器
0
投稿

猜你喜欢

  • Java中常见的陷阱题及答案

    2021-08-10 16:32:11
  • Spring源码解析 Bean属性填充

    2021-06-28 19:17:16
  • 浅谈idea live template高级知识_进阶(给方法,类,js方法添加注释)

    2023-12-07 22:50:28
  • 如何用.NETCore操作RabbitMQ

    2022-06-20 04:11:04
  • Java泛型T,E,K,V,N,?与Object区别和含义

    2022-02-23 07:25:30
  • 深入解析Java的Spring框架中bean的依赖注入

    2023-12-20 18:50:52
  • 全面了解Java中Native关键字的作用

    2022-07-13 02:01:09
  • Android 优雅的实现通用格式化编辑

    2023-02-08 05:24:02
  • Java利用TreeUtils工具类实现列表转树

    2021-10-02 03:28:00
  • 实例详解用户输入 i. 检测常用手势

    2022-04-10 19:22:55
  • Java接口RandomAccess全面了解

    2023-09-11 19:05:29
  • IDEA的Mybatis Generator驼峰配置问题

    2023-03-24 02:21:32
  • C#中值类型和引用类型解析

    2023-02-10 22:41:33
  • Java程序图形用户界面设计之按钮与布局

    2023-07-18 07:03:21
  • java中functional interface的分类和使用详解

    2021-09-15 15:59:20
  • SSH框架网上商城项目第24战之Struts2中处理多个Model请求的方法

    2023-02-14 20:49:21
  • Gradle修改本地仓库的位置方法实现

    2022-01-17 21:25:52
  • 小菜编程成长记(一 面试受挫——代码无错就是好?)第1/3页

    2023-06-08 19:15:26
  • SpringBoot整合MongoDB的步骤详解

    2023-10-11 17:20:32
  • 深入理解C# DateTime日期格式化

    2021-12-08 04:23:17
  • asp之家 软件编程 m.aspxhome.com