-
Notifications
You must be signed in to change notification settings - Fork 138
/
modJson.bas
529 lines (475 loc) · 15.8 KB
/
modJson.bas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
Attribute VB_Name = "modJson"
Option Explicit
'VB6 原生JSON解析库,没有引用JS库 <https://github.com/0xAA55/VB6JSON>
'使用方法:
'调用 ParseJSONString() 函数或者 ParseJSONString2() 过程来解析 JSON 字符串,得到一个返回的 Variant 类型的变量
'数值类型被解析为 Long 或者 Currency,取决于数值的范围,数值比较小就用 Long,比较大就用 Currency,而如果数值包含小数点或者科学计数法,则使用 Double 类型。
'JSON 字符串被解析为 VB 字符串,其中字符串的转义字符 \ 会按规范进行转义。
'列表 [] 被解析为 VB6 的 Variant 数组,每个数组元素都可以是不同的类型。
'对象 {} 被解析为字典(Scripting.Dictionary)。注意 对象变量需要用 Set 来赋值。
'函数 JSONToString() 做相反的工作:把解析出来的 Variant 转换回 JSON 字符串。
'ParseJSONString: 使用函数返回值保存解析后的Variant,要根据目标类型,使用"set varName=ParseJSONString(str)"或"varName=ParseJSONString(str)"
'ParseJSONString2: 使用传入参数返回,不管是否是对象类型都可以正常工作,ParseJSONString2(str, retVariant)
'得到解析结果后,如何判断 Variant 的具体类型?
'先判断它是不是对象,使用 IsObject() 来判断。
'如果不是对象,此时判断它是不是数组,使用 IsArray() 来判断。
'如果不是数组,则需要判断它是不是字符串,先用 VarType() 来获取 Variant 的类型号,然后判断类型号是不是 vbString。
'不能直接用 IsNumeric() 进行判断,因为它会把字符串格式存储的数值也判定为是数值。
'如果不是字符串,那它应该是数值了。根据刚才调用的 VarType() 的返回值,可以判断它是否为 Long、Currency、Double。类型号分别为 vbLong、vbCurrency、vbDouble。
Private Type ParserContext
JSONString As String
I As Long
Length As Long
LineNo As Long
Column As Long
End Type
Const JSONErrCode As Long = 2000
Private Function IsSpace(ByVal Char As String) As Boolean
IsSpace = True
Select Case Char
Case vbCr
Case vbLf
Case vbTab
Case " "
Case Else
IsSpace = False
End Select
End Function
Private Function GetPositionString(Ctx As ParserContext) As String
GetPositionString = "line " & Ctx.LineNo & " column " & Ctx.Column
End Function
Private Function IsEndOfString(Ctx As ParserContext) As Boolean
IsEndOfString = Ctx.I > Ctx.Length
End Function
Private Function PeekChar(Ctx As ParserContext) As String
PeekChar = Mid$(Ctx.JSONString, Ctx.I, 1)
End Function
Private Sub SkipChar(Ctx As ParserContext, PeekedChar As String)
Ctx.I = Ctx.I + 1
If PeekedChar = vbLf Then
Ctx.LineNo = Ctx.LineNo + 1
Ctx.Column = 1
Else
Ctx.Column = Ctx.Column + 1
End If
End Sub
Private Function GetChar(Ctx As ParserContext) As String
GetChar = PeekChar(Ctx)
SkipChar Ctx, GetChar
End Function
Private Sub SkipSpaces(Ctx As ParserContext)
Dim CurChar As String
Do
CurChar = PeekChar(Ctx)
If IsSpace(CurChar) = False Then Exit Do
SkipChar Ctx, CurChar
Loop
End Sub
Private Function HexCharToVal(ByVal HexCharAsc As Long) As Long
If HexCharAsc >= &H30 And HexCharAsc <= &H39 Then
HexCharToVal = HexCharAsc - &H30
ElseIf HexCharAsc >= &H41 And HexCharAsc <= &H46 Then
HexCharToVal = HexCharAsc - &H41 + 10
ElseIf HexCharAsc >= &H61 And HexCharAsc <= &H66 Then
HexCharToVal = HexCharAsc - &H61 + 10
Else
Err.Raise JSONErrCode, "JSON Parser"
End If
End Function
Private Function ParseString(Ctx As ParserContext, Optional ByVal IsObjectKey As Boolean = False) As String
Dim CurChar As String
Dim Escape As Boolean
Dim EscapeHex As Boolean
Dim HexNumDigits As Long
Dim HexVal As Long
Dim StartLineNo As Long, StartColumn As Long
StartLineNo = Ctx.LineNo
StartColumn = Ctx.Column - 1
Do
CurChar = GetChar(Ctx)
If Len(CurChar) = 0 Then Err.Raise JSONErrCode, "JSON Parser", "Unterminated string starting at " & "line " & StartLineNo & " column " & StartColumn
If Escape Then
If EscapeHex Then
HexVal = HexVal * &H10 + HexCharToVal(AscW(CurChar))
HexNumDigits = HexNumDigits + 1
If HexNumDigits = 4 Then
If IsObjectKey And HexVal < &H20 Then Err.Raise JSONErrCode, "JSON Parser", "Invalid control character at " & GetPositionString(Ctx)
ParseString = ParseString & ChrW$(HexVal)
EscapeHex = False
Escape = False
End If
Else
Escape = False
Select Case CurChar
Case """"
ParseString = ParseString & CurChar
Case "\"
ParseString = ParseString & CurChar
Case "/"
ParseString = ParseString & CurChar
Case "b"
ParseString = ParseString & vbBack
Case "f"
ParseString = ParseString & vbFormFeed
Case "n"
ParseString = ParseString & vbLf
Case "r"
ParseString = ParseString & vbCr
Case "t"
ParseString = ParseString & vbTab
Case "u"
Escape = True
EscapeHex = True
HexNumDigits = 0
HexVal = 0
Err.Description = "Invalid \uXXXX escape at " & GetPositionString(Ctx)
Case Else
Err.Raise JSONErrCode, "JSON Parser", "Invalid \escape at " & GetPositionString(Ctx)
End Select
End If
Else
If IsObjectKey And AscW(CurChar) < &H20 Then Err.Raise JSONErrCode, "JSON Parser", "Invalid control character at " & GetPositionString(Ctx)
If CurChar = "\" Then
Escape = True
ElseIf CurChar = """" Then
Exit Do
Else
ParseString = ParseString & CurChar
End If
End If
Loop
End Function
Private Function GetNumeric(Ctx As ParserContext) As String
Dim CurChar As String
Do
CurChar = PeekChar(Ctx)
If IsNumeric(CurChar) Then
GetNumeric = GetNumeric & CurChar
SkipChar Ctx, CurChar
Else
Exit Do
End If
Loop
If Len(GetNumeric) = 0 Then Err.Raise JSONErrCode, "JSON Parser", "Expecting value at " & GetPositionString(Ctx)
End Function
Private Function NumericToInteger(Numeric As String) As Variant
On Error GoTo Try1
NumericToInteger = CLng(Numeric)
Exit Function
Try1:
NumericToInteger = CCur(Numeric)
End Function
Private Function NumericToVariant(Numeric As String) As Variant
On Error GoTo Try1
NumericToVariant = NumericToInteger(Numeric)
Exit Function
Try1:
NumericToVariant = CDbl(Numeric)
End Function
Private Function ParseNumber(Ctx As ParserContext, ByVal FirstChar As String) As Variant
Dim IsSigned As Boolean
Dim NumberString As String
Dim CurChar As String
Dim IsSignedExp As Boolean
If FirstChar = "-" Then
IsSigned = True
SkipChar Ctx, FirstChar
End If
NumberString = GetNumeric(Ctx)
ParseNumber = NumericToVariant(NumberString)
CurChar = PeekChar(Ctx)
If CurChar = "." Then
SkipChar Ctx, CurChar
NumberString = GetNumeric(Ctx)
ParseNumber = CDbl(ParseNumber) + CDbl(NumberString) / (10 ^ Len(NumberString))
End If
CurChar = PeekChar(Ctx)
If LCase$(CurChar) = "e" Then
SkipChar Ctx, CurChar
CurChar = PeekChar(Ctx)
If CurChar = "-" Then
SkipChar Ctx, CurChar
IsSignedExp = True
End If
NumberString = GetNumeric(Ctx)
If IsSignedExp Then NumberString = "-" & NumberString
ParseNumber = CDbl(ParseNumber) * (10 ^ CDbl(NumberString))
End If
If IsSigned Then ParseNumber = -ParseNumber
End Function
Function IsEmptyArray(TestArray As Variant) As Boolean
IsEmptyArray = True
On Local Error Resume Next
Dim I As Long
I = -1
I = UBound(TestArray)
If I >= 0 Then IsEmptyArray = False
End Function
Private Function ParseList(Ctx As ParserContext) As Variant
Dim CurChar As String
Dim RetList() As Variant
Dim ItemCount As Long
SkipSpaces Ctx
CurChar = PeekChar(Ctx)
If CurChar = "]" Then
SkipChar Ctx, CurChar
ParseList = RetList
Exit Function
End If
ReDim RetList(8)
Do
ParseSubString Ctx, RetList(ItemCount)
ItemCount = ItemCount + 1
If ItemCount >= UBound(RetList) + 1 Then ReDim Preserve RetList(ItemCount * 3 / 2 + 1)
SkipSpaces Ctx
CurChar = PeekChar(Ctx)
If CurChar = "]" Then
SkipChar Ctx, CurChar
If ItemCount Then
ReDim Preserve RetList(ItemCount - 1)
Else
Erase RetList
End If
ParseList = RetList
Exit Function
ElseIf CurChar = "," Then
SkipChar Ctx, CurChar
Else
Err.Raise JSONErrCode, "JSON Parser", "Unexpected `" & CurChar & "` at " & GetPositionString(Ctx)
End If
Loop
End Function
Private Function ParseObject(Ctx As ParserContext) As Variant
Dim JObject As Object
Dim SubItem As Variant
Dim CurChar As String
Set JObject = CreateObject("Scripting.Dictionary")
SkipSpaces Ctx
CurChar = PeekChar(Ctx)
If CurChar = "}" Then
SkipChar Ctx, CurChar
Set ParseObject = JObject
Exit Function
End If
Dim KeyName As String
Do
CurChar = PeekChar(Ctx)
If CurChar = """" Then
SkipChar Ctx, CurChar
KeyName = ParseString(Ctx, True)
ElseIf CurChar = "'" Then
Err.Raise JSONErrCode, "JSON Parser", "Expecting property name enclosed in double quotes at " & GetPositionString(Ctx)
Else
Err.Raise JSONErrCode, "JSON Parser", "Key name must be string at " & GetPositionString(Ctx)
End If
SkipSpaces Ctx
CurChar = PeekChar(Ctx)
If CurChar <> ":" Then Err.Raise JSONErrCode, "JSON Parser", "Expecting ':' delimiter at " & GetPositionString(Ctx)
SkipChar Ctx, CurChar
SkipSpaces Ctx
ParseSubString Ctx, SubItem
JObject.Add KeyName, SubItem
SkipSpaces Ctx
CurChar = PeekChar(Ctx)
If CurChar = "}" Then
SkipChar Ctx, CurChar
Exit Do
ElseIf CurChar = "," Then
SkipChar Ctx, CurChar
SkipSpaces Ctx
Else
Err.Raise JSONErrCode, "JSON Parser", "Expecting ',' delimiter at " & GetPositionString(Ctx)
End If
Loop
Set ParseObject = JObject
End Function
Private Function ParseBoolean(Ctx As ParserContext, ByVal ExpectedValue As Boolean) As Variant
Dim CurChar As String
Dim Word As String, ExpectedWord As String
Dim I As Long
If ExpectedValue = False Then
ExpectedWord = "false"
Else
ExpectedWord = "true"
End If
For I = 1 To Len(ExpectedWord)
CurChar = GetChar(Ctx)
If Len(CurChar) Then Word = Word & CurChar Else Err.Raise JSONErrCode, "JSON Parser", "Expecting value at " & GetPositionString(Ctx)
Next
If Word = ExpectedWord Then
ParseBoolean = ExpectedValue
Else
Err.Raise JSONErrCode, "JSON Parser", "Unknown identifier `" & Word & "` at " & GetPositionString(Ctx)
End If
End Function
Private Function ParseNull(Ctx As ParserContext) As Variant
Dim CurChar As String
Dim Word As String, ExpectedWord As String
Dim I As Long
ExpectedWord = "null"
For I = 1 To Len(ExpectedWord)
CurChar = GetChar(Ctx)
If Len(CurChar) Then Word = Word & CurChar Else Err.Raise JSONErrCode, "JSON Parser", "Expecting value at " & GetPositionString(Ctx)
Next
If Word <> ExpectedWord Then
Err.Raise JSONErrCode, "JSON Parser", "Unknown identifier `" & Word & "` at " & GetPositionString(Ctx)
End If
End Function
Private Sub ParseSubString(Ctx As ParserContext, outParsed As Variant)
SkipSpaces Ctx
If IsEndOfString(Ctx) Then Err.Raise JSONErrCode, "JSON Parser", "Expecting value at " & GetPositionString(Ctx)
Dim CurChar As String
CurChar = PeekChar(Ctx)
If CurChar = """" Then
SkipChar Ctx, CurChar
outParsed = ParseString(Ctx)
ElseIf IsNumeric(CurChar) = True Or CurChar = "-" Then
outParsed = ParseNumber(Ctx, CurChar)
ElseIf CurChar = "[" Then
SkipChar Ctx, CurChar
outParsed = ParseList(Ctx)
ElseIf CurChar = "{" Then
SkipChar Ctx, CurChar
Set outParsed = ParseObject(Ctx)
ElseIf CurChar = "t" Then
outParsed = ParseBoolean(Ctx, True)
ElseIf CurChar = "f" Then
outParsed = ParseBoolean(Ctx, False)
ElseIf CurChar = "n" Then
outParsed = ParseNull(Ctx)
Else
Err.Raise JSONErrCode, "JSON Parser", "Unexpected `" & CurChar & "` at " & GetPositionString(Ctx)
End If
End Sub
Private Function NewParserContext(JSONString As String) As ParserContext
With NewParserContext
.JSONString = JSONString
.I = 1
.Length = Len(JSONString)
.LineNo = 1
.Column = 1
End With
End Function
Function ParseJSONString(JSONString As String) As Variant
Dim Ctx As ParserContext
Ctx = NewParserContext(JSONString)
ParseSubString Ctx, ParseJSONString
SkipSpaces Ctx
'commented by cdhigh, ignore error
'If IsEndOfString(Ctx) = False Then Err.Raise JSONErrCode, "JSON Parser", "Extra data at " & GetPositionString(Ctx)
End Function
Sub ParseJSONString2(JSONString As String, ReturnParsed As Variant)
Dim Ctx As ParserContext
Ctx = NewParserContext(JSONString)
ParseSubString Ctx, ReturnParsed
SkipSpaces Ctx
'commented by cdhigh, ignore error
'If IsEndOfString(Ctx) = False Then Err.Raise JSONErrCode, "JSON Parser", "Extra data at " & GetPositionString(Ctx)
End Sub
Private Function Hex4(ByVal Value As Long) As String
Hex4 = Right$("000" & Hex$(Value), 4)
End Function
Private Function EscapeString(ByVal SourceStr As String) As String
Dim I As Long, EI As Long, CurChar As String, CharCode As Long, ToAppend As String
EI = Len(SourceStr)
For I = 1 To EI
CurChar = Mid$(SourceStr, I, 1)
CharCode = CLng(AscW(CurChar)) And &HFFFF&
Select Case CharCode
Case 0
ToAppend = "\0"
Case 1 To 7, &HB, &HE To &H1F, &HD800& To &HDFFF&
ToAppend = "\u" & Hex4(CharCode)
Case 8
ToAppend = "\b"
Case 9
ToAppend = "\t"
Case &HA
ToAppend = "\n"
Case &HC
ToAppend = "\f"
Case &HD
ToAppend = "\r"
Case &H22
ToAppend = "\"""
Case &H5C
ToAppend = "\\"
Case Else
ToAppend = CurChar
End Select
EscapeString = EscapeString & ToAppend
Next
End Function
Function JSONToString(JSONData As Variant, Optional ByVal Indent As Long = 0, Optional ByVal IndentChar = " ", Optional ByVal CurIndentLevel As Long = 0) As String
If IsArray(JSONData) Then
If IsEmptyArray(JSONData) Then
JSONToString = "[]"
Exit Function
End If
Dim I As Long, U As Long
U = UBound(JSONData)
JSONToString = "["
CurIndentLevel = CurIndentLevel + 1
If Indent Then GoSub IndentNextLine
For I = 0 To U
JSONToString = JSONToString & JSONToString(JSONData(I), Indent, IndentChar, CurIndentLevel + 1)
If I <> U Then
JSONToString = JSONToString & ","
If Indent Then GoSub IndentNextLine
End If
Next
CurIndentLevel = CurIndentLevel - 1
If Indent Then GoSub IndentNextLine
JSONToString = JSONToString & "]"
ElseIf IsObject(JSONData) Then
Dim JObj As Object, KeyName As Variant, IsNotFirst As Boolean
Set JObj = JSONData
If JObj.Count = 0 Then
JSONToString = "{}"
Exit Function
End If
JSONToString = "{"
If Indent Then GoSub IndentNextLine
For Each KeyName In JObj
If IsNotFirst Then
JSONToString = JSONToString & ","
If Indent Then GoSub IndentNextLine
End If
JSONToString = JSONToString & """" & KeyName & """: " & JSONToString(JObj(KeyName), Indent, IndentChar, CurIndentLevel + 1)
IsNotFirst = True
Next
CurIndentLevel = CurIndentLevel - 1
If Indent Then GoSub IndentNextLine
JSONToString = JSONToString & "}"
Else
Select Case VarType(JSONData)
Case vbString
JSONToString = """" & EscapeString(JSONData) & """"
Case vbEmpty
JSONToString = "null"
Case Else
If IsNumeric(JSONData) Then
JSONToString = JSONData
If Left$(JSONToString, 1) = "." Then
JSONToString = "0" & JSONToString
Else
JSONToString = Replace(JSONToString, "-.", "-0.")
End If
JSONToString = Replace(LCase$(JSONToString), "e+", "e")
Else
Err.Raise JSONErrCode, "JSON Parser", "Unknown variant type `" & VarType(JSONData) & "`"
End If
End Select
End If
Exit Function
AddIndent:
JSONToString = JSONToString & String(Indent * CurIndentLevel, IndentChar)
Return
AddNewLine:
JSONToString = JSONToString & vbCrLf
Return
IndentNextLine:
GoSub AddNewLine
GoSub AddIndent
Return
End Function