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

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,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • C#在Unity游戏开发中进行多线程编程的方法

    在这之前,有很多人在质疑Unity支不支持多线程,事实上Unity是支持多线程的.而提到多线程就要提到Unity非常常用的协程,然而协程并非真正的多线程.协程其实是等某个操作完成之后再执行后面的代码,或者说是控制代码在特定的时机执行.而多线程在Unity渲染和复杂逻辑运算时可以高效的使用多核CPU,帮助程序可以更高效的运行.本篇主要介绍在Unity中如何使用多线程. 首先引入C#中使用多线程的类库 using System.Threading; 创建线程实例的四种方式 一.线程执行无参方法 构造

  • Unity C#打包AssetBundle与场景详解

    Unity2018已经把打包过程简化很多了 我们只需要关心两个API: 1.BuildPipline.BuildAssetBundles() 打包AssetBundle 2.BuildPipline.BuildPlayer() 打包场景 1.打包AssetBundle 先在资源的Inspector面板最下方 填写资源所属的AssetBundle名称和后缀(后缀可以不填) 再利用BuildPipeline.BuildAssetBundles()进行打包 2.打包Scene 利用BuildPipel

  • C#中Socket与Unity相结合示例代码

    前言 初步接触了Socket,现使其与Unity相结合,做成一个简单的客户端之间可以互相发送消息的一个Test.下面话不多说了,来一起看看详细的介绍吧. 方法如下: 首先,是服务端的代码. 创建一个连接池,用于存储客户端的数量. using System; using System.Net; using System.Net.Sockets; using System.Collections; using System.Collections.Generic; namespace Server

  • 利用unity代码C#封装为dll的步骤分享

    前言 本文主要介绍了关于unity代码C#封装为dll的相关内容,分享出来供需要的朋友们学习,下面话不多说了,来一起学习学习吧. 方法如下 1 Visual studio软件打开后创建一个项目 2并选择类库类型 3编写简单的代码看看效果(发现会报错),主要是没有添加类库,以及using UnityEngine;引用空间 4添加类库,引用空间 发现依然会报错,这就需要添加应用库unityengine.dll,方法如下 找到安装unity目录下的UnityEngine.dll,添加后你就会发现,报红

  • Unity中C#和Java的相互调用实例代码

    1.通过C#调用Java的方法: 在C#中添加调用的一些代码,利用Unity提供的一些接口实现调用Java! private const string JAVA_CLASS_Name = "com.unity3d.player.UnityPlayer"; private void CallJavaFunc(string javaFuncName, params object[] args) { try { //获取到AndroidJavaClass,至于这里为什么调用这个类,我也不是很

  • C#语言使用Unity实现剪刀石头布游戏

    本文实例为大家分享了C#语言使用Unity实现剪刀石头布游戏的具体代码,供大家参考,具体内容如下 游戏:剪刀石头布 实现功能: 1.电脑随机出牌(剪刀石头布) 2.玩家选择出牌(剪刀石头布) 3.玩家没有出牌时,电脑变幻牌面: 玩家出牌后,电脑出牌,并停止变幻牌面3秒,期间玩家无法选择出牌 4.玩家和电脑出牌后,电脑自动计分. using UnityEngine; using System.Collections; public class hw0310a : MonoBehaviour { /

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

    MojoUnityJson 是使用C#实现的JSON解析器 ,算法思路来自于游戏引擎Mojoc的C语言实现 Json.h .借助C#的类库,可以比C的实现更加的简单和全面,尤其是处理Unicode Code(\u开头)字符的解析,C#的StringBuilder本身就支持了UnicodeCodePoint. MojoUnityJson使用递归下降的解析模式,核心解析代码只有450行(去掉空行可能只有300多行),支持标准的JSON格式.算法实现力求简洁明了,用最直接最快速的方法达到目的,没有复杂

  • c# 如何实现一个简单的json解析器

    一.JSON格式介绍 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着很多优点.例如易读性更好,占用空间更少等.在 web 应用开发领域内,得益于 JavaScript 对 JSON 提供的良好支持,JSON 要比 XML 更受开发人员青睐.所以作为开发人员,如果有兴趣的话,还是应该深入了解一下 JSON 相关的知识.本着探究 JSON 原理的目的,我将会在这篇文章中详细向大家介绍一个简单的JSON解析

  • Go语言JSON解析器gjson使用方法详解

    目录 gjson 安装 使用 gjson GJSON 是一个Go包,它提供了一种从json文档中获取值的快速简单的方法.它具有单行检索.点符号路径.迭代和解析 json 行等功能. 还可以查看SJSON以修改 json,以及JJ命令行工具. 本自述文件是如何使用 GJSON 的快速概述,有关更多信息,请查看GJSON 语法. github 的地址在这里. 安装 安装gjson,使用的是go传统的安装方法: go install github.com/tidwall/gjson@latest 在文

  • SpringBoot使用自定义json解析器的使用方法

    Spring-Boot是基于Spring框架的,它并不是对Spring框架的功能增强,而是对Spring的一种快速构建的方式. Spring-boot应用程序提供了默认的json转换器,为Jackson.示例: pom.xml中dependency配置: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  • 对Java中JSON解析器的一些见解

    最近在研究JSON,Java中有很多处理JSON的类库,lib-json.sf-json.fastjson还有Jackson Json.第一个就不说了,性能和功能都没有什么亮点. sf-json最大的优点就是随机读取方便.代码很简单: JSONObject json= JSONObject.fromObject(str); 然后读取字段内容: json.getString或者getInt之类的.但是工作效率有待商榷,而且容易出错. 另外sf-json还有个优点就是自动使用unicode编码,当内

  • 浅析ASP.NET万能JSON解析器

    概念介绍还是先简单说说Json的一些例子吧.注意,以下概念是我自己定义的,可以参考.net里面的TYPE的模型设计如果有争议,欢迎提出来探讨! 1.最简单:{"total":0} total就是值,值是数值,等于0 2. 复杂点{"total":0,"data":{"377149574" : 1}}total是值,data是对象,这个对象包含了"377149574"这个值,等于1 3. 最复杂{"

  • 使用70行Python代码实现一个递归下降解析器的教程

     第一步:标记化 处理表达式的第一步就是将其转化为包含一个个独立符号的列表.这一步很简单,且不是本文的重点,因此在此处我省略了很多. 首先,我定义了一些标记(数字不在此中,它们是默认的标记)和一个标记类型: token_map = {'+':'ADD', '-':'ADD', '*':'MUL', '/':'MUL', '(':'LPAR', ')':'RPAR'} Token = namedtuple('Token', ['name', 'value']) 下面就是我用来标记 `expr` 表

  • Spring boot中自定义Json参数解析器的方法

    一.介绍 用过springMVC/spring boot的都清楚,在controller层接受参数,常用的都是两种接受方式,如下 /** * 请求路径 http://127.0.0.1:8080/test 提交类型为application/json * 测试参数{"sid":1,"stuName":"里斯"} * @param str */ @RequestMapping(value = "/test",method = Re

  • java实现简单解析XML文件功能示例

    本文实例讲述了java实现简单解析XML文件功能.分享给大家供大家参考,具体如下: package demo; import java.io.File; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException;

  • Python3自定义json逐层解析器代码

    用python3对json内容逐层进行解析,拿中国天气网的接口返回数据测试, 代码如下: # -*- coding: utf-8 -*- import operator as op from collections import defaultdict class Json(object): def __init__(self, json: str): sth = eval(json) load = lambda sth: sth if op.eq(type(sth).__name__, dic

随机推荐