浅谈C#手动解析Json格式内容

这个应该算处女贴吧 - -

之前百度了许久基本没有一个满意的json结构的解析类库 想了想还是自己做一个吧

现在我来说下大概的思路 首先我创建了一个 JsonTokener的类 用于处理json字符串的一些操作里面有个枚举

 1     public enum JsonCharType
 2     {
 3         BeginObject = 123, //{
 4         EndObject = 125, //}
 5         BeginArray = 91, //[
 6         EndArray = 93, //]
 7         DoubleQuote = 34, //"
 8         SingleQuote = 39, //
 9         Comma = 44,//,
10         Split = 58, //:
11         Slash = 92, //\
12         BackSlash = 47, ///
13         Right = 13, //\r
14         Line = 10, //\n
15         None = -1 //结尾或异常
16     }

这些枚举数值代表了循环到的char状态 后面都有注释说明每个值代表的字符

  1  public sealed class JsonTokener
  2     {
  3         private string jsonSource = string.Empty;
  4         private int currentIndex = -1;
  5 
  6         public int CurrentIndex
  7         {
  8             get { return currentIndex; }
  9             set { currentIndex = value; }
 10         }
 11         private int countIndex = 0;
 12 
 13         internal int lastIndex = 0;
 14 
 15         public JsonTokener(string jsonSource)
 16         {
 17             this.jsonSource = jsonSource;
 18             currentIndex = -1;
 19             countIndex = jsonSource != null ? jsonSource.Length : 0;
 20         }
 21 
 22         /// <summary>
 23         /// 向前推进一个字符
 24         /// </summary>
 25         /// <returns></returns>
 26         public JsonCharType next()
 27         {
 28             currentIndex++;
 29             return getCurrentIndex();
 30         }
 31 
 32         public JsonCharType next(params JsonCharType[] types)
 33         {
 34             JsonCharType currentType = next();
 35             while ((int)currentType != -1)
 36             {
 37                 if (checkType(currentType, types))
 38                 {
 39                     return currentType;
 40                 }
 41                 else
 42                 {
 43                     currentType = next();
 44                 }
 45             }
 46             return JsonCharType.None;
 47         }
 48 
 49         private bool checkType(JsonCharType currentType, JsonCharType[] types)
 50         {
 51             foreach (JsonCharType item in types)
 52             {
 53                 if (currentType == item)
 54                     return true;
 55             }
 56             return false;
 57         }
 58 
 59         /// <summary>
 60         /// 向后推后一个字符
 61         /// </summary>
 62         /// <returns></returns>
 63         public JsonCharType back()
 64         {
 65             currentIndex--;
 66             return getCurrentIndex();
 67         }
 68 
 69         /// <summary>
 70         /// 获得当前位置字符
 71         /// </summary>
 72         /// <returns></returns>
 73         public JsonCharType current()
 74         {
 75             return getCurrentIndex();
 76         }
 77 
 78         /// <summary>
 79         /// 获得当前位置的字符
 80         /// </summary>
 81         /// <returns></returns>
 82         private JsonCharType getCurrentIndex()
 83         {
 84             if (currentIndex >= 0 && currentIndex < countIndex)
 85             {
 86                 char c = jsonSource[currentIndex];
 87                 return (JsonCharType)Enum.ToObject(typeof(JsonCharType), c);
 88             }
 89             else
 90             {
 91                 currentIndex = countIndex;
 92             }
 93             //else 验证当前索引是否小于0
 94             return JsonCharType.None;
 95         }
 96 
 97         /// <summary>
 98         /// 查找该位置到下一个type间隔的文本
 99         /// </summary>
100         /// <param name="type"></param>
101         /// <returns></returns>
102         public string nextToType(JsonCharType type)
103         {
104             StringBuilder builder = new StringBuilder();
105             JsonCharType currentType = next();
106             while ((int)currentType != -1)
107             {
108                 if (currentType == type)
109                 {
110                     return builder.ToString();
111                 }
112                 else
113                 {
114                     builder.Append((char)currentType);
115                     currentType = next();
116                 }
117             }
118             throw new Exception("已到字符串末尾 没有找到相关匹配");
119         }
120     }

其中的操作无疑就代表了位移字符等一些操作 返回基本都是以JsonCharType为准

然后我又写了个类叫JsonSerialization 用于解析json结构

在此我就贴核心代码了 剩下的大家自己研究 要是都给出来就没意思了~

在这里我只提供思路

在JsonSerialization里有个FindObject的方法 还有个Deserializa用于解析json结构 方法内有两个参数一个是JsonObject是我自己定义的 还有一个是 JsonTokener就是上面的类

public JsonObject Deserializa()
{
    JsonTokener tokener = new JsonTokener(jsonSource);
    JsonObject parentNode = new JsonObject();
    //解析object
    FindObject(parentNode, tokener);
    return parentNode;
}

这个FindObject 基本实现思路

JsonCharType type = tokener.next(
JsonCharType.BeginArray,
JsonCharType.BeginObject,
JsonCharType.EndArray,
JsonCharType.EndObject,
JsonCharType.SingleQuote,
JsonCharType.DoubleQuote,
JsonCharType.Split,
JsonCharType.Comma);

首先寻找这些字符

一般一个完整的json结构传入都会以 { [ 开头 以] }结束 为了做到完美适配各种json结构 我加入了单引号识别和双引号识别

现在假设一个json结构 jsonp({"str_key":"value", "bool_test":true,"int_test":123,"double_test":12.7,"null_test":null,"array_test":["array1","array2",{"array_obj" : "array_obj"},["inner_array",1,12.2,true,null]]}) 当然上述的json结构是以jsonp跨域返回的串做的例子 我只是想证明 我做的这个解析会忽略掉jsonp头也就是说从第一个{开始解析或者第一个[开始解析

好了扯远了 既然找到了第一个列表包含的字符 那么就要进行进一步搜寻 也就是说还需要递归调用FindObject

所以第一个if也就有了

if (type == JsonCharType.BeginObject)
{
    if (parentNode.IsArray)
    {
        JsonObject innerObj = new JsonObject();
        //innerObj._parent = parentNode;
        innerObj._isObject = true;
        innerObj._sourceObj = new Dictionary<string, JsonObject>();
        FindObject(innerObj, tokener);
        parentNode.add(innerObj);
        //继续寻找
        FindObject(parentNode, tokener);
    }
    else
    {
        parentNode._isObject = true;
        parentNode._sourceObj = new Dictionary<string, JsonObject>();
        FindObject(parentNode, tokener);
    }
}

为什么要有个parentNode.IsArray的判断呢 是为了防止从上次递归过来的JsonObject为Array类型 然而递归到这里发现是Object的开头

所以只需要新构建个JsonObject然后在寻找更下层的层级嵌套 然后添加进去 在继续寻找直到找完位置

那个else的意思是代表了如果这个是初始化的操作也就是说一个没有经过任何设置的JsonObject进行一个初始化并开始递归寻找

else if (type == JsonCharType.BeginArray)
{
    tokener.lastIndex = tokener.CurrentIndex;
    if (parentNode.IsObject)
    {
        JsonObject innerObj = new JsonObject();
        //innerObj._parent = parentNode;
        FindObject(innerObj, tokener);
        parentNode.add(innerObj);
        //继续寻找
        FindObject(parentNode, tokener);
    }
    else
    {
        if (parentNode.IsArray)
        {
            JsonObject innerObj = new JsonObject();
            //innerObj._parent = parentNode;
            innerObj._isArray = true;
            innerObj._sourceObj = new List<JsonObject>();
            FindObject(innerObj, tokener);
            parentNode.add(innerObj);
            //继续寻找
            FindObject(parentNode, tokener);
        }
        else
        {
            parentNode._isArray = true;
            parentNode._sourceObj = new List<JsonObject>();
            FindObject(parentNode, tokener);
        }
    }
}

这段代码是接上面的我一段一段的解释

这段代码意思是当发现起始json array结构时 判断 上一个递归过来的节点是不是json object类型 如果是就构建一个新的JsonObject并添加

如果不是的话判断上一个节点过来的是不是json array结构 如果是json array结构则又需要构建一个JsonObject 并添加 

否则进行初始化设定并继续寻找

else if (type == JsonCharType.DoubleQuote || type == JsonCharType.SingleQuote)
{
    if (parentNode.IsObject)
    {
        //找寻key
        string _key = tokener.nextToType(type);
        JsonObject innerObj = new JsonObject();
        //innerObj._parent = parentNode;
        innerObj._key = _key;
        FindObject(innerObj, tokener);
        parentNode.add(innerObj);
        //继续寻找
        FindObject(parentNode, tokener);
    }
    else if (parentNode.IsArray)
    {
        //找到value并添加value
        string _value = tokener.nextToType(type);
        JsonObject obj = new JsonObject();
        //obj._parent = parentNode;
        obj.convertToJsonObject(_value, false);
        parentNode.add(obj);
        tokener.lastIndex = tokener.CurrentIndex;
        //继续寻找
        FindObject(parentNode, tokener);
    }
    else
    {
        //找到value并设置value
        string _value = tokener.nextToType(type);
        parentNode.convertToJsonObject(_value, false);
    }
    tokener.lastIndex = tokener.CurrentIndex;
}

重头戏来了 关于单引号 以及双引号的解析  首先判断递归节点 这几乎成为了每个判断必经之路- -

如果来自json object结构 则 先寻找key就相当于 循环到了这里 "str_key":"value" 当然后面内容是我自动脑补的

json 的标准key value结构 当然value可以是任意结构可能是json object可能是json array也可能是json int

所以既然key的结构固定了那我就从当前的单引号或者双引号寻找下一个单引号或双引号这取决于起始标记

也就是 string _key = tokener.nextToType(type); 这个type自然是搜索到的单或双引号~

上面我也说了key的结构是固定的 但是value的结构不固定所以我还得调用FindObject并构建一个新的JsonObject结构

并且把key赋值过去 当找到value的时候继续寻找- -

如果父节点是IsArray类型基本原理和上面差不多只不过省去了找寻key的过程因为json array类型不需要key~

当找到最后就只剩value的设置了当然这只是设置json string类型至于 json int 啊 json null json double都还得另作设定

else if (type == JsonCharType.Split) //遇到kv分隔符表明了parentNode是object类型
{
    tokener.lastIndex = tokener.CurrentIndex;
    //进一步验证如果真的是object则继续找寻
    FindObject(parentNode, tokener);
}

接着走到这里 大家可能发现一行频繁出现的代码 tokener.lastIndex = tokener.CurrentIndex; 可别小看这一行接下来就会用到

else if (type == JsonCharType.Comma) //遇到逗号表明 parentNode有可能是任意类型
{
    tokener.CurrentIndex = tokener.lastIndex;
    string value = tokener.nextToType(type);
    value = value.Trim();
    tokener.lastIndex = tokener.CurrentIndex;
    if (value == string.Empty)
    {
        //进一步验证
        FindObject(parentNode, tokener);
    }
    else
    {
        if (parentNode.IsArray)
        {
            JsonObject innerObj = new JsonObject();
            //innerObj._parent = parentNode;
            innerObj.convertToJsonObject(value, true);
            parentNode.add(innerObj);
            FindObject(parentNode, tokener);
        }
        else
        {
            parentNode.convertToJsonObject(value, true);
        }
    }
}

恩在这里这个tokener.lastIndex就派上用场了 这个值的含义是在一些指定的操作记录最后一次的字符索引 比如" ‘ { [ ] } : 等符号都会进行记录这是为了解析json array或者非json string类型的数值

设置tokener的当前位置为上一次特殊操作的最后index

也就是tokener.CurrentIndex = tokener.lastIndex;这句

然后获取value 也就是说比如tokener操作进行到了这一步], \r\n1, "asd", true ] 等

假设最后特殊操作在]然后我需要截取到,这里就会获取一个空值所以我做了个验证如果为空则继续FindObject

如果不为空 就会被解析为" \r\n1"所以我加了个Trim这样可以过滤掉前后的空格\r\n等数据

然后进行进一步验证如果父节点来自json array则创建个新的JsonObject并把这个value进行一个内部的convert 进行int 类型 double类型 string类型等特殊类型的识别这个大家可以想想怎么做~

找寻到最后递归到最后的else  parentNode.convertToJsonObject(value, true); 将读出的 非json string类型转换为 JsonObject

else if (type == JsonCharType.EndArray || type == JsonCharType.EndObject)
{
    tokener.lastIndex = tokener.CurrentIndex;
}

这是最后一个else了可算完工了 如果为结束array 或者结束object 也就是]} 记录最后一次特殊操作为了应对](ps:记录这个位置然后下次递归寻找的时候就会为空然后继续寻找真正的value), \r\n1, "asd", true ]

 

总的来说这就是一个近乎完整的json解析 自己实现的思路以及一些代码 奥对了在内部转换的过程中有的json 串是这种格式 \u2223这种unicode编码的特殊字符串

我在这提供一个解析方法写的可能效率不是很好如果有好的建议留言~

this._isString = true;
int idx = str.IndexOf("\\u");
while (idx != -1)
{
    string v = str.Substring(idx, 6);
    string hex1 = v.Substring(2, 2);
    string hex2 = v.Substring(4);
    byte[] bytes = new byte[2] {
        Convert.ToByte(hex2,16), //高低位转换
        Convert.ToByte(hex1,16) //所以是反的~
    };
    str = str.Replace(v, Encoding.Unicode.GetString(bytes));
    idx = str.IndexOf("\\u");
}
_sourceObj = str;

当然上面的解析也只是小弟的拙计毕竟现在比较好的解析都不是很好用无奈只能自己写~并提供下思路~

处女贴完结- -

浅谈C#手动解析Json格式内容

上一篇:hosts文件配置及主要作用


下一篇:C#基础---事件的使用