好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

原文链接:https://HdhCmsTestjianshu测试数据/p/76a441d1ac44

dy("nrwz");

查看更多关于C#实现JSON解析器MojoUnityJson功能(简单且高效)的详细内容...

  阅读:39次