如何識別一個字串是否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;//長度比索引+1103 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