原文:
如何识别一个字符串是否Json格式
前言:
距离上一篇文章,又过去一个多月了,近些时间,工作依旧很忙碌,除了管理方面的事,代码方面主要折腾三个事:
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,仅方便自己使用。
夜已深,该闭眼去梦里的世界旅游了。
最后是本文的源码:
1
using System;
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 }
View Code