C#实现JSON解析器MojoUnityJson功能(简单且高效)
MojoUnityJson是使用C#实现的JSON解析器,算法思路来自于游戏引擎Mojoc的C语言实现Json.h。借助C#的类库,可以比C的实现更加的简单和全面,尤其是处理UnicodeCode(\u开头)字符的解析,C#的StringBuilder本身就支持了UnicodeCodePoint。
MojoUnityJson使用递归下降的解析模式,核心解析代码只有450行(去掉空行可能只有300多行),支持标准的JSON格式。算法实现力求简洁明了,用最直接最快速的方法达到目的,没有复杂的概念和模式。除了解析JSON,还提供了一组方便直观的API来访问JSON数据,整体实现只有一个文件,仅依赖System.Collections.Generic,System.Text,System三个命名空间,MojoUnityJson可以很容易的嵌入到其它项目里使用。
本文主要介绍一下,超级简单又高效,并且看一眼就完全明白的解析算法,几乎可以原封不动的复制粘贴成其它语言版本的实现。
保存上下文信息
使用一个简单的结构体,用来在解析的过程中,传递一些上下文数据。
privatestructData
{
//需要解析的JSON字符串
publicstringjson;
//当前JSON字符串解析的位置索引
publicintindex;
//缓存一个StringBuilder,用来抠出JSON的一段字符。
publicStringBuildersb;
publicData(stringjson,intindex)
{
this.json=json;
this.index=index;
this.sb=newStringBuilder();
}
}
抽象JSON的值
我们把JSON的值抽象成以下几个类型:
publicenumJsonType
{
Object,
Array,
String,
Number,
Bool,
Null,
}
整体解析步骤
//解析JsonValue privatestaticJsonValueParseValue(refDatadata); //解析JsonObject privatestaticJsonValueParseObject(refDatadata); //解析JsonArray privatestaticJsonValueParseArray(refDatadata); //解析string privatestaticJsonValueParseString(refDatadata); //解析number privatestaticJsonValueParseNumber(refDatadata)
这就是全部的解析流程,在ParseValue中会根据字符判断类型,分别调用下面几个不同的解析函数。JsonValue就对应一个JSON的值,它有一个JsonType代表了这个值的类型。这是一个递归的过程,在ParseValue,ParseObject和ParseArray过程中,会递归的调用ParseValue。JSON一定是始于一个,Object或Array,当这个最顶层的值解析完毕的时候,整个JSON也就解析完成了。
解析空白字符
解析过程中,会有很多为了格式化存在的空白字符,需要剔除这些,才能获得有信息的字符,这是一个重复的过程,需要一个函数统一处理。
privatestaticvoidSkipWhiteSpace(refDatadata)
{
while(true)
{
switch(data.json[data.index])
{
case'':
case'\t':
case'\n':
case'\r':
data.index++;//每次消耗一个字符,就向后推进JSON的索引
continue;
}
break;
}
}
解析JsonValue
privatestaticJsonValueParseValue(refDatadata)
{
//跳过空白字符
SkipWhiteSpace(refdata);
varc=data.json[data.index];
switch(c)
{
case'{':
//表示Object
returnParseObject(refdata);
case'[':
//表示Array
returnParseArray(refdata);
case'"':
//表示string
returnParseString(refdata);
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
case'8':
case'9':
case'-':
//表示数值
returnParseNumber(refdata);
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
returnnewJsonValue(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
returnnewJsonValue(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
returnnewJsonValue(JsonType.Null,null);
}
break;
}
//不能处理了
thrownewException(string.Format("JsonParseValueerroronchar'{0}'indexin'{1}'",c,data.index));
}
- ParseValue是解析的主入口,代表着解析JsonValue这个抽象的JSON值,其真实的类型在解析的过程中逐渐具体化。
- 在剥离掉空白字符之后,就可以很容易的通过单个字符,就判断出其可能的数值类型,而不需要向前或向后检索。
- true,false,null这几个固定的类型,直接就处理掉了,而其它稍微复杂的类型需要使用函数来处理。
- 这里没有使用ifelse,而是大量使用了case,是为了提高效率,减少判断次数。
解析JsonObject
privatestaticJsonValueParseObject(refDatadata)
{
//Object对应C#的Dictionary
varjsonObject=newDictionary(JsonObjectInitCapacity);
//skip'{'
data.index++;
do
{
//跳过空白字符
SkipWhiteSpace(refdata);
if(data.json[data.index]=='}')
{
//空的Object,"{}"
break;
}
DebugTool.Assert
(
data.json[data.index]=='"',
"JsonParseObjecterror,char'{0}'shouldbe'\"'",
data.json[data.index]
);
//skip'"'
data.index++;
varstart=data.index;
//解析Object的key值
while(true)
{
varc=data.json[data.index++];
switch(c)
{
case'"':
//checkend'"'
break;
case'\\':
//skipescapedquotes
data.index++;
continue;
default:
continue;
}
//alreadyskiptheend'"'
break;
}
//getobjectkeystring
//扣出key字符串
varkey=data.json.Substring(start,data.index-start-1);
//跳过空白
SkipWhiteSpace(refdata);
DebugTool.Assert
(
data.json[data.index]==':',
"JsonParseObjecterror,afterkey={0},char'{1}'shouldbe':'",
key,
data.json[data.index]
);
//skip':'
data.index++;
//setJsonObjectkeyandvalue
//递归的调用ParseValue获得Object的value值
jsonObject.Add(key,ParseValue(refdata));
//跳过空白
SkipWhiteSpace(refdata);
if(data.json[data.index]==',')
{
//Object的下一对KV
data.index++;
}
else
{
//跳过空白
SkipWhiteSpace(refdata);
DebugTool.Assert
(
data.json[data.index]=='}',
"JsonParseObjecterror,afterkey={0},char'{1}'shouldbe'{2}'",
key,
data.json[data.index],
'}'
);
break;
}
}
while(true);
//skip'}'andreturnafter'}'
data.index++;
returnnewJsonValue(JsonType.Object,jsonObject);
}
JsonObject类型就简单的对应C#的Dictionary,value是JsonValue类型。当解析完成后,value的类型就是确定的了。
JsonValue是递归的调用ParseValue来处理的,其类型可能是JsonType枚举的任意类型。
解析JsonArray
privatestaticJsonValueParseArray(refDatadata)
{
//JsonArray对应List
varjsonArray=newList(JsonArrayInitCapacity);
//skip'['
data.index++;
do
{
//跳过空白
SkipWhiteSpace(refdata);
if(data.json[data.index]==']')
{
//空"[]"
break;
}
//addJsonArrayitem
//递归处理List每个元素
jsonArray.Add(ParseValue(refdata));
//跳过空白
SkipWhiteSpace(refdata);
if(data.json[data.index]==',')
{
//解析下一个元素
data.index++;
}
else
{
//跳过空白
SkipWhiteSpace(refdata);
DebugTool.Assert
(
data.json[data.index]==']',
"JsonParseArrayerror,char'{0}'shouldbe']'",
data.json[data.index]
);
break;
}
}
while(true);
//skip']'
data.index++;
returnnewJsonValue(JsonType.Array,jsonArray);
}
JsonArray类型就简单的对应C#的List,element是JsonValue类型。当解析完成后,element的类型就是确定的了。
JsonValue是递归的调用ParseValue来处理的,其类型可能是JsonType枚举的任意类型。
解析string
privatestaticJsonValueParseString(refDatadata)
{
//skip'"'
data.index++;
varstart=data.index;
stringstr;
//处理字符串
while(true)
{
switch(data.json[data.index++])
{
case'"'://字符串结束
//checkend'"'
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();
//clearfornextstring
//清空字符,供下次使用
data.sb.Length=0;
}
break;
case'\\':
{
//checkescapedchar
varescapedIndex=data.index;
charc;
//处理各种转义字符
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]
);
//skipcodepoint
data.index+=4;
break;
default:
//notsupportjustaddinprestring
continue;
}
//addprestringandescapedchar
//特殊处理的字符和正常的字符,一起放入StringBuilder
data.sb.Append(data.json,start,escapedIndex-start-1).Append(c);
//updateprestringstartindex
start=data.index;
continue;
}
default:
continue;
}
//alreadyskiptheend'"'
break;
}
returnnewJsonValue(JsonType.String,str);
}
处理字符串麻烦的地方在于,转义字符需要特殊处理,都这转义字符就会直接显示而不能展示特殊的作用。好在StringBuilder功能非常强大,提供处理各种情况的接口。
解析Unicode字符
在JSON中,Unicode字符是以\u开头跟随4个码点组成的转义字符。码点在StringBuilder的Append重载函数中是直接支持的。所以,我们只要把\u后面的4个字符,转换成码点传递给Append就可以了。
//////Gettheunicodecodepoint. /// privatestaticcharGetUnicodeCodePoint(charc1,charc2,charc3,charc4) { //把\u后面的4个char转换成码点,注意这里需要是char类型,才能被Append正确处理。 //4个char转换为int后,映射到16进制的高位到低位,然后相加得到码点。 return(char) ( UnicodeCharToInt(c1)*0x1000+ UnicodeCharToInt(c2)*0x100+ UnicodeCharToInt(c3)*0x10+ UnicodeCharToInt(c4) ); } //////Singleunicodecharconverttoint. /// privatestaticintUnicodeCharToInt(charc) { //使用switchcase减少ifelse的判断 switch(c) { case'0': case'1': case'2': case'3': case'4': case'5': case'6': case'7': case'8': case'9': returnc-'0'; case'a': case'b': case'c': case'd': case'e': case'f': returnc-'a'+10; case'A': case'B': case'C': case'D': case'E': case'F': returnc-'A'+10; } thrownewException(string.Format("JsonUnicodechar'{0}'error",c)); }
解析number
privatestaticJsonValueParseNumber(refDatadata)
{
varstart=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;
}
//抠出数值字符串
varstrNum=data.json.Substring(start,data.index-start);
floatnum;
//当成float处理,当然也可以用double
if(float.TryParse(strNum,outnum))
{
returnnewJsonValue(JsonType.Number,num);
}
else
{
thrownewException(string.Format("JsonParseNumbererror,cannotparsestring[{0}]",strNum));
}
}
如何使用
只有一句话,把Json字符串解析成JsonValue对象,然后JsonValue对象包含了所有的数值。
varjsonValue=MojoUnity.Json.Parse(jsonString); JsonValue的访问API //JsonValue当做string publicstringAsString(); //JsonValue当做float publicfloatAsFloat(); //JsonValue当做int publicfloatAsInt(); //JsonValue当做bool publicfloatAsBool(); //JsonValue当做null publicfloatIsNull(); //JsonValue当做Dictionary publicDictionaryAsObject(); //JsonValue当做Dictionary并根据key获取value当做JsonValue publicJsonValueAsObjectGet(stringkey); //JsonValue当做Dictionary并根据key获取value当做Dictionary publicDictionary AsObjectGetObject(stringkey); //JsonValue当做Dictionary并根据key获取value当做List publicList AsObjectGetArray(stringkey); //JsonValue当做Dictionary并根据key获取value当做string publicstringAsObjectGetString(stringkey); //JsonValue当做Dictionary并根据key获取value当做float publicfloatAsObjectGetFloat(stringkey); //JsonValue当做Dictionary并根据key获取value当做int publicintAsObjectGetInt(stringkey); //JsonValue当做Dictionary并根据key获取value当做bool publicboolAsObjectGetBool(stringkey); //JsonValue当做Dictionary并根据key获取value当做null publicboolAsObjectGetIsNull(stringkey); //JsonValue当做List publicList AsArray(); //JsonValue当做List并获取index的value当做JsonValue publicJsonValueAsArrayGet(intindex); //JsonValue当做List并获取index的value当做Dictionary publicDictionary AsArrayGetObject(intindex); //JsonValue当做List并获取index的value当做List publicList AsArrayGetArray(intindex); //JsonValue当做List并获取index的value当做string publicstringAsArrayGetString(intindex); //JsonValue当做List并获取index的value当做float publicfloatAsArrayGetFloat(intindex); //JsonValue当做List并获取index的value当做int publicintAsArrayGetInt(intindex); //JsonValue当做List并获取index的value当做bool publicboolAsArrayGetBool(intindex); //JsonValue当做List并获取index的value当做null publicboolAsArrayGetIsNull(intindex);
最后
MojoUnityJson目的就是完成简单而单一的JSON字符串解析功能,能够读取JSON的数据就是最重要的功能。在网上也了解了一些开源的C#实现的JSON库,不是功能太多太丰富,就是实现有些繁琐了,于是就手动实现了MojoUnityJson。
总结
以上所述是小编给大家介绍的C#实现JSON解析器MojoUnityJson,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!