前言:
距离上一篇文章,又过去一个多月了,近些时间,工作依旧很忙碌,除了管理方面的事,代码方面主要折腾三个事:
1:开发框架(一整套基于配置型的开发体系框架)
2:CYQ.Data 数据层框架(持续的更新,最近也加入了Sybase的支持)
3:工作流流程图设计器。
由于这三个方面都涉及到Json,所以就谈谈这些天在Json上花下的心思。
关于造轮子:
很多人对于造轮子都会有自己的看法,这里提一下个人的观点:
个人认为:
1:首要是要具备造轮子的能力,然后再讨论造不造与浪不浪、轮子与时间的问题。
2:造轮子的、写文章的,永远比使用轮子的、看文章的,多经历了一些、多思考一些、多知道一些。
所以,别嫌造轮子折腾,虽然的确很折腾,不是有那么句:生命在于折腾,除了瞎折腾。
PS:本来文章是写Json常用的功能交互那块相关的知识,所以才有这一段。
不多扯了,扯多了都是蛋,还是回归正题吧。
如何识别一个字符串是不是Json。
网上搜了一下,找到两三个坑人的答案:
A:Js识别,Eval一下,成功就是,失败就挂。
B:C#识别,判断开始和结束符号:{}或[]
C:用正则表达式判断。
上面ABC答案都纯忽悠,只要认真一下,都不靠谱了。
经过我的研究,发现这是有很有挑战性的课题:
Json需要分析的情况,比想象的要多,举一个不太简单的Json:
[1,{"a":2},\r\n{"a":{}}, {"a":[]},{"a":[{}]},{"{[a":"\"2,:3,"a":33}]"}]
从上面这个Json中,就可以看出需要分析的有:
1:数组和Json数组。
2:键与值(无引号、双引号)的识别
3:无限级值嵌套(数组嵌套、Json嵌套)
4:7个关键符号[{,:"}]。
5:转义符号、空格、换行、回车处理。
回顾早些年写的JsonHelper
还记得CYQ.Data里JsonHelper的最初版本,仅处理了只有一级Json的简单情况,那时候分析Json就靠以下两种方法:
1:Split 分隔。
2:循环 indexOf 识别。
虽然偷工减料,投机取巧,但只要限定使用环境和条件、好在够用,也够简单。
当然了,现在情况变了,把限定的环境和条件去除后,事实上,要分析起来就没那么简单了。
故事一开始,思考了三天三夜
由于放开了条件,需要考虑无限级递归的,于是看似Split和IndexOf这种方式已经不奏效了。
字符串的分析方法看似需要改朝换代了,但我仍给Split和IndexOf寻求最后的机会。
经过层层思考与分析,发经没折了,只有祭出终极必杀招了。
终极大招:遍历字符,记录状态
一个万能的解决方法,就是遍历每个字符,然后记录这个字符前后左右上下东南西北中发白各种状态,再根据状态来识别下一个字符的动作。
1:首先有一个记录字符状态的类,如下图:
这个字符状态的记录类,我前后不断调整了N天,才终于感觉好像OK了。
2:接下来是字符的状态设置,根据不同的关键字,设置状态,如下图:
这是个漫长不断调试的过程,很折腾人。
3:一个可以不断递归Json的函数,如下图:
4:一个可以识别语法错误的函数:
5:最后是一个给外部的调用方法:
总结:
虽然本文是关于识别Json格式,实际上,它已经是Json解析类的核心,用它可以演化出Json的各种应用,有机会再介绍了。
事实上, 一开始是原打算写Json与Xml互转那一块的,写文的意原来自最近一周折腾工作流的流程设计器那一块:
从Xml出来到前端成为Json,编辑完后回去又要转回原始格式的Xml存档,所以在Xml和Json间,必须有一套协议,这些,大概是时间不够,所以临时变了一个题目。
关于Json的在线解析,以及Json和Xml和互转,临时我开了个域名 :tool.cyqdata.com,仅方便自己使用。
夜已深,该闭眼去梦里的世界旅游了。
最后是本文的源码:
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace CYQ.Data.Tool
6 {
7 /// <summary>
8 /// 分隔Json字符串为字典集合。
9 /// </summary>
10 internal class JsonSplit
11 {
12 private static bool IsJsonStart(ref string json)
13 {
14 if (!string.IsNullOrEmpty(json))
15 {
16 json = json.Trim('\r', '\n', ' ');
17 if (json.Length > 1)
18 {
19 char s = json[0];
20 char e = json[json.Length - 1];
21 return (s == '{' && e == '}') || (s == '[' && e == ']');
22 }
23 }
24 return false;
25 }
26 internal static bool IsJson(string json)
27 {
28 int errIndex;
29 return IsJson(json, out errIndex);
30 }
31 internal static bool IsJson(string json, out int errIndex)
32 {
33 errIndex = 0;
34 if (IsJsonStart(ref json))
35 {
36 CharState cs = new CharState();
37 char c;
38 for (int i = 0; i < json.Length; i++)
39 {
40 c = json[i];
41 if (SetCharState(c, ref cs) && cs.childrenStart)//设置关键符号状态。
42 {
43 string item = json.Substring(i);
44 int err;
45 int length = GetValueLength(item, true, out err);
46 cs.childrenStart = false;
47 if (err > 0)
48 {
49 errIndex = i + err;
50 return false;
51 }
52 i = i + length - 1;
53 }
54 if (cs.isError)
55 {
56 errIndex = i;
57 return false;
58 }
59 }
60
61 return !cs.arrayStart && !cs.jsonStart;
62 }
63 return false;
64 }
65
66 /// <summary>
67 /// 获取值的长度(当Json值嵌套以"{"或"["开头时)
68 /// </summary>
69 private static int GetValueLength(string json, bool breakOnErr, out int errIndex)
70 {
71 errIndex = 0;
72 int len = 0;
73 if (!string.IsNullOrEmpty(json))
74 {
75 CharState cs = new CharState();
76 char c;
77 for (int i = 0; i < json.Length; i++)
78 {
79 c = json[i];
80 if (!SetCharState(c, ref cs))//设置关键符号状态。
81 {
82 if (!cs.jsonStart && !cs.arrayStart)//json结束,又不是数组,则退出。
83 {
84 break;
85 }
86 }
87 else if (cs.childrenStart)//正常字符,值状态下。
88 {
89 int length = GetValueLength(json.Substring(i), breakOnErr, out errIndex);//递归子值,返回一个长度。。。
90 cs.childrenStart = false;
91 cs.valueStart = 0;
92 //cs.state = 0;
93 i = i + length - 1;
94 }
95 if (breakOnErr && cs.isError)
96 {
97 errIndex = i;
98 return i;
99 }
100 if (!cs.jsonStart && !cs.arrayStart)//记录当前结束位置。
101 {
102 len = i + 1;//长度比索引+1
103 break;
104 }
105 }
106 }
107 return len;
108 }
109 /// <summary>
110 /// 字符状态
111 /// </summary>
112 private class CharState
113 {
114 internal bool jsonStart = false;//以 "{"开始了...
115 internal bool setDicValue = false;// 可以设置字典值了。
116 internal bool escapeChar = false;//以"\"转义符号开始了
117 /// <summary>
118 /// 数组开始【仅第一开头才算】,值嵌套的以【childrenStart】来标识。
119 /// </summary>
120 internal bool arrayStart = false;//以"[" 符号开始了
121 internal bool childrenStart = false;//子级嵌套开始了。
122 /// <summary>
123 /// 【0 初始状态,或 遇到“,”逗号】;【1 遇到“:”冒号】
124 /// </summary>
125 internal int state = 0;
126
127 /// <summary>
128 /// 【-1 取值结束】【0 未开始】【1 无引号开始】【2 单引号开始】【3 双引号开始】
129 /// </summary>
130 internal int keyStart = 0;
131 /// <summary>
132 /// 【-1 取值结束】【0 未开始】【1 无引号开始】【2 单引号开始】【3 双引号开始】
133 /// </summary>
134 internal int valueStart = 0;
135 internal bool isError = false;//是否语法错误。
136
137 internal void CheckIsError(char c)//只当成一级处理(因为GetLength会递归到每一个子项处理)
138 {
139 if (keyStart > 1 || valueStart > 1)
140 {
141 return;
142 }
143 //示例 ["aa",{"bbbb":123,"fff","ddd"}]
144 switch (c)
145 {
146 case '{'://[{ "[{A}]":[{"[{B}]":3,"m":"C"}]}]
147 isError = jsonStart && state == 0;//重复开始错误 同时不是值处理。
148 break;
149 case '}':
150 isError = !jsonStart || (keyStart != 0 && state == 0);//重复结束错误 或者 提前结束{"aa"}。正常的有{}
151 break;
152 case '[':
153 isError = arrayStart && state == 0;//重复开始错误
154 break;
155 case ']':
156 isError = !arrayStart || jsonStart;//重复开始错误 或者 Json 未结束
157 break;
158 case '"':
159 case '\'':
160 isError = !(jsonStart || arrayStart); //json 或数组开始。
161 if (!isError)
162 {
163 //重复开始 [""",{"" "}]
164 isError = (state == 0 && keyStart == -1) || (state == 1 && valueStart == -1);
165 }
166 if (!isError && arrayStart && !jsonStart && c == '\'')//['aa',{}]
167 {
168 isError = true;
169 }
170 break;
171 case ':':
172 isError = !jsonStart || state == 1;//重复出现。
173 break;
174 case ',':
175 isError = !(jsonStart || arrayStart); //json 或数组开始。
176 if (!isError)
177 {
178 if (jsonStart)
179 {
180 isError = state == 0 || (state == 1 && valueStart > 1);//重复出现。
181 }
182 else if (arrayStart)//["aa,] [,] [{},{}]
183 {
184 isError = keyStart == 0 && !setDicValue;
185 }
186 }
187 break;
188 case ' ':
189 case '\r':
190 case '\n'://[ "a",\r\n{} ]
191 case '\0':
192 case '\t':
193 break;
194 default: //值开头。。
195 isError = (!jsonStart && !arrayStart) || (state == 0 && keyStart == -1) || (valueStart == -1 && state == 1);//
196 break;
197 }
198 //if (isError)
199 //{
200
201 //}
202 }
203 }
204 /// <summary>
205 /// 设置字符状态(返回true则为关键词,返回false则当为普通字符处理)
206 /// </summary>
207 private static bool SetCharState(char c, ref CharState cs)
208 {
209 cs.CheckIsError(c);
210 switch (c)
211 {
212 case '{'://[{ "[{A}]":[{"[{B}]":3,"m":"C"}]}]
213 #region 大括号
214 if (cs.keyStart <= 0 && cs.valueStart <= 0)
215 {
216 cs.keyStart = 0;
217 cs.valueStart = 0;
218 if (cs.jsonStart && cs.state == 1)
219 {
220 cs.childrenStart = true;
221 }
222 else
223 {
224 cs.state = 0;
225 }
226 cs.jsonStart = true;//开始。
227 return true;
228 }
229 #endregion
230 break;
231 case '}':
232 #region 大括号结束
233 if (cs.keyStart <= 0 && cs.valueStart < 2 && cs.jsonStart)
234 {
235 cs.jsonStart = false;//正常结束。
236 cs.state = 0;
237 cs.keyStart = 0;
238 cs.valueStart = 0;
239 cs.setDicValue = true;
240 return true;
241 }
242 // cs.isError = !cs.jsonStart && cs.state == 0;
243 #endregion
244 break;
245 case '[':
246 #region 中括号开始
247 if (!cs.jsonStart)
248 {
249 cs.arrayStart = true;
250 return true;
251 }
252 else if (cs.jsonStart && cs.state == 1)
253 {
254 cs.childrenStart = true;
255 return true;
256 }
257 #endregion
258 break;
259 case ']':
260 #region 中括号结束
261 if (cs.arrayStart && !cs.jsonStart && cs.keyStart <= 2 && cs.valueStart <= 0)//[{},333]//这样结束。
262 {
263 cs.keyStart = 0;
264 cs.valueStart = 0;
265 cs.arrayStart = false;
266 return true;
267 }
268 #endregion
269 break;
270 case '"':
271 case '\'':
272 #region 引号
273 if (cs.jsonStart || cs.arrayStart)
274 {
275 if (cs.state == 0)//key阶段,有可能是数组["aa",{}]
276 {
277 if (cs.keyStart <= 0)
278 {
279 cs.keyStart = (c == '"' ? 3 : 2);
280 return true;
281 }
282 else if ((cs.keyStart == 2 && c == '\'') || (cs.keyStart == 3 && c == '"'))
283 {
284 if (!cs.escapeChar)
285 {
286 cs.keyStart = -1;
287 return true;
288 }
289 else
290 {
291 cs.escapeChar = false;
292 }
293 }
294 }
295 else if (cs.state == 1 && cs.jsonStart)//值阶段必须是Json开始了。
296 {
297 if (cs.valueStart <= 0)
298 {
299 cs.valueStart = (c == '"' ? 3 : 2);
300 return true;
301 }
302 else if ((cs.valueStart == 2 && c == '\'') || (cs.valueStart == 3 && c == '"'))
303 {
304 if (!cs.escapeChar)
305 {
306 cs.valueStart = -1;
307 return true;
308 }
309 else
310 {
311 cs.escapeChar = false;
312 }
313 }
314
315 }
316 }
317 #endregion
318 break;
319 case ':':
320 #region 冒号
321 if (cs.jsonStart && cs.keyStart < 2 && cs.valueStart < 2 && cs.state == 0)
322 {
323 if (cs.keyStart == 1)
324 {
325 cs.keyStart = -1;
326 }
327 cs.state = 1;
328 return true;
329 }
330 // cs.isError = !cs.jsonStart || (cs.keyStart < 2 && cs.valueStart < 2 && cs.state == 1);
331 #endregion
332 break;
333 case ',':
334 #region 逗号 //["aa",{aa:12,}]
335
336 if (cs.jsonStart)
337 {
338 if (cs.keyStart < 2 && cs.valueStart < 2 && cs.state == 1)
339 {
340 cs.state = 0;
341 cs.keyStart = 0;
342 cs.valueStart = 0;
343 //if (cs.valueStart == 1)
344 //{
345 // cs.valueStart = 0;
346 //}
347 cs.setDicValue = true;
348 return true;
349 }
350 }
351 else if (cs.arrayStart && cs.keyStart <= 2)
352 {
353 cs.keyStart = 0;
354 //if (cs.keyStart == 1)
355 //{
356 // cs.keyStart = -1;
357 //}
358 return true;
359 }
360 #endregion
361 break;
362 case ' ':
363 case '\r':
364 case '\n'://[ "a",\r\n{} ]
365 case '\0':
366 case '\t':
367 if (cs.keyStart <= 0 && cs.valueStart <= 0) //cs.jsonStart &&
368 {
369 return true;//跳过空格。
370 }
371 break;
372 default: //值开头。。
373 if (c == '\\') //转义符号
374 {
375 if (cs.escapeChar)
376 {
377 cs.escapeChar = false;
378 }
379 else
380 {
381 cs.escapeChar = true;
382 return true;
383 }
384 }
385 else
386 {
387 cs.escapeChar = false;
388 }
389 if (cs.jsonStart || cs.arrayStart) // Json 或数组开始了。
390 {
391 if (cs.keyStart <= 0 && cs.state == 0)
392 {
393 cs.keyStart = 1;//无引号的
394 }
395 else if (cs.valueStart <= 0 && cs.state == 1 && cs.jsonStart)//只有Json开始才有值。
396 {
397 cs.valueStart = 1;//无引号的
398 }
399 }
400 break;
401 }
402 return false;
403 }
404 }
405 }
补充内容:
发现本文访问量比较高,以上的源码在后期又有所更新,所以放出最新源码所在的地址:
https://github.com/cyq1162/cyqdata/blob/master/Tool/JsonSplit.cs
版权声明:本文原创发表于 博客园,作者为 路过秋天 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。 |
个人微信公众号 |
Donation(扫码支持作者):支付宝: |
Donation(扫码支持作者):微信: |