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