前言:
距離上一篇文章,又過去一個多月了,近些時間,工作依舊很忙碌,除了管理方面的事,代碼方面主要折騰三個事:
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