如何识别一个字符串是否Json格式

2年前 (2022) 程序员胖胖胖虎阿
387 0 0

前言:

距离上一篇文章,又过去一个多月了,近些时间,工作依旧很忙碌,除了管理方面的事,代码方面主要折腾三个事:

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:首先有一个记录字符状态的类,如下图:

如何识别一个字符串是否Json格式 

这个字符状态的记录类,我前后不断调整了N天,才终于感觉好像OK了。 

2:接下来是字符的状态设置,根据不同的关键字,设置状态,如下图:

如何识别一个字符串是否Json格式 

这是个漫长不断调试的过程,很折腾人。

3:一个可以不断递归Json的函数,如下图:

如何识别一个字符串是否Json格式

4:一个可以识别语法错误的函数:

如何识别一个字符串是否Json格式

5:最后是一个给外部的调用方法:

如何识别一个字符串是否Json格式 

总结:

虽然本文是关于识别Json格式,实际上,它已经是Json解析类的核心,用它可以演化出Json的各种应用,有机会再介绍了。

事实上, 一开始是原打算写Json与Xml互转那一块的,写文的意原来自最近一周折腾工作流的流程设计器那一块:

如何识别一个字符串是否Json格式 

从Xml出来到前端成为Json,编辑完后回去又要转回原始格式的Xml存档,所以在Xml和Json间,必须有一套协议,这些,大概是时间不够,所以临时变了一个题目。 

 

关于Json的在线解析,以及Json和Xml和互转,临时我开了个域名 :tool.cyqdata.com,仅方便自己使用。

 

夜已深,该闭眼去梦里的世界旅游了。 

最后是本文的源码:

如何识别一个字符串是否Json格式

  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, trueout 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

补充内容:

发现本文访问量比较高,以上的源码在后期又有所更新,所以放出最新源码所在的地址:

https://github.com/cyq1162/cyqdata/blob/master/Tool/JsonSplit.cs

版权声明:程序员胖胖胖虎阿 发表于 2022年10月24日 上午3:40。
转载请注明:如何识别一个字符串是否Json格式 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...