Last active
November 29, 2023 18:26
-
-
Save t3rminus/d278175893dea1ecfed83c29df56a9d8 to your computer and use it in GitHub Desktop.
A JSON parser in VBS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Class VbsJson | |
'Author: Demon (http://demon.tw), 2012-5-3 | |
'Modified by: t3rminus, 2019-11-20 | |
'Changelog: | |
' - Fix bug relating to empty array properties in object | |
' - Encode not tested | |
' - Removed use of Set keyword that may cause issues in some environments | |
Private Whitespace, NumberRegex, StringChunk | |
Private b, f, r, n, t | |
Private Sub Class_Initialize | |
Whitespace = " " & vbTab & vbCr & vbLf | |
b = ChrW( 8 ) | |
f = vbFormFeed | |
r = vbCr | |
n = vbLf | |
t = vbTab | |
NumberRegex = New RegExp | |
NumberRegex.Pattern = "(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?" | |
NumberRegex.Global = False | |
NumberRegex.MultiLine = True | |
NumberRegex.IgnoreCase = True | |
StringChunk = New RegExp | |
StringChunk.Pattern = "([\s\S]*?)([""\\\x00-\x1f])" | |
StringChunk.Global = False | |
StringChunk.MultiLine = True | |
StringChunk.IgnoreCase = True | |
End Sub | |
'Return a JSON string representation of a VBScript data structure | |
'Supports the following objects and types | |
'+------------+--------+ | |
'| VBScript | JSON | | |
'+------------+--------+ | |
'| Dictionary | object | | |
'| Array | array | | |
'| String | string | | |
'| Number | number | | |
'| True | true | | |
'| False | false | | |
'| Null | null | | |
'+------------+--------+ | |
Public Function Encode( ByRef obj) | |
Dim buf, i, c, g | |
buf = CreateObject ( "Scripting.Dictionary" ) | |
Select Case VarType (obj) | |
Case vbNull | |
buf.Add buf.Count, "null" | |
Case vbBoolean | |
If obj Then | |
buf.Add buf.Count, "true" | |
Else | |
buf.Add buf.Count, "false" | |
End If | |
Case vbInteger , vbLong , vbSingle , vbDouble | |
buf.Add buf.Count, obj | |
Case vbString | |
buf.Add buf.Count, """" | |
For i = 1 To Len (obj) | |
c = Mid( obj, i, 1 ) | |
Select Case c | |
Case """" buf.Add buf.Count, "\""" | |
Case "\" buf.Add buf.Count, "\\" | |
Case "/" buf.Add buf.Count, "/" | |
Case b buf.Add buf.Count, "\b" | |
Case f buf.Add buf.Count, "\f" | |
Case r buf.Add buf.Count, "\r" | |
Case n buf.Add buf.Count, "\n" | |
Case t buf.Add buf.Count, "\t" | |
Case Else | |
If AscW(c) >= 0 And AscW(c) <= 31 Then | |
c = Right ( "0" & Hex (AscW(c)), 2 ) | |
buf.Add buf.Count, "\u00" & c | |
Else | |
buf.Add buf.Count, c | |
End If | |
End Select | |
Next | |
buf.Add buf.Count, """" | |
Case vbArray + vbVariant | |
g = True | |
buf.Add buf.Count, "[" | |
For Each i In obj | |
If g Then g = False Else buf.Add buf.Count, "," | |
buf.Add buf.Count, Encode(i) | |
Next | |
buf.Add buf.Count, "]" | |
Case vbObject | |
If TypeName (obj) = "Dictionary" Then | |
g = True | |
buf.Add buf.Count, "{" | |
For Each i In obj | |
If g Then g = False Else buf.Add buf.Count, "," | |
buf.Add buf.Count, """" & i & """" & ":" & Encode(obj(i)) | |
Next | |
buf.Add buf.Count, "}" | |
Else | |
Err.Raise 8732 ,, "None dictionary object" | |
End If | |
Case Else | |
buf.Add buf.Count, """" & CStr (obj) & """" | |
End Select | |
Encode = Join (buf.Items, "" ) | |
End Function | |
'Return the VBScript representation of ``str(`` | |
'Performs the following translations in decoding | |
'+--------+------------+ | |
'| JSON | VBScript | | |
'+--------+------------+ | |
'| object | Dictionary | | |
'| array | Array | | |
'| string | String | | |
'| number | Number | | |
'| true | True | | |
'| false | False | | |
'| null | Null | | |
'+--------+------------+ | |
Public Function Decode( ByRef str) | |
Dim idx | |
Idx = SkipWhitespace(str, 1 ) | |
Decode = ScanOnce(str, 1 ) | |
End Function | |
Private Function ScanOnce( ByRef str, ByRef idx) | |
Dim c, ms | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "{" Then | |
Idx = idx + 1 | |
ScanOnce = ParseObject(str, idx) | |
Exit Function | |
ElseIf c = "[" Then | |
Idx = idx + 1 | |
ScanOnce = ParseArray(str, idx) | |
Exit Function | |
ElseIf c = """" Then | |
Idx = idx + 1 | |
ScanOnce = ParseString(str, idx) | |
Exit Function | |
ElseIf c = "n" And StrComp ( "null" , Mid (str, idx, 4 )) = 0 Then | |
Idx = idx + 4 | |
ScanOnce = Null | |
Exit Function | |
ElseIf c = "t" And StrComp ( "true" , Mid (str, idx, 4 )) = 0 Then | |
Idx = idx + 4 | |
ScanOnce = True | |
Exit Function | |
ElseIf c = "f" And StrComp ( "false" , Mid (str, idx, 5 )) = 0 Then | |
Idx = idx + 5 | |
ScanOnce = False | |
Exit Function | |
End If | |
ms = NumberRegex.Execute( Mid (str, idx)) | |
If ms.Count = 1 Then | |
Idx = idx + ms( 0 ).Length | |
ScanOnce = CDbl (ms( 0 )) | |
Exit Function | |
End If | |
Err.Raise 8732 ,, "No JSON object could be ScanOnced" | |
End Function | |
Private Function ParseObject( ByRef str, ByRef idx) | |
Dim c, key, value | |
ParseObject = CreateObject ( "Scripting.Dictionary" ) | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "}" Then | |
Idx = idx + 1 | |
Exit Function | |
ElseIf c <> """" Then | |
Err.Raise 8732 ,, "Expecting property name" | |
End If | |
Idx = idx + 1 | |
Do | |
Key = ParseString(str, idx) | |
Idx = SkipWhitespace(str, idx) | |
If Mid( str, idx, 1 ) <> ":" Then | |
Err.Raise 8732 ,, "Expecting : delimiter" | |
End If | |
Idx = SkipWhitespace(str, idx + 1 ) | |
value = ScanOnce(str, idx) | |
ParseObject.Add key, value | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "}" Then | |
Exit Do | |
ElseIf c <> "," Then | |
Err.Raise 8732 ,, "Expecting , delimiter in Object, found " & c | |
End If | |
Idx = SkipWhitespace(str, idx + 1 ) | |
c = Mid( str, idx, 1 ) | |
If c <> """" Then | |
Err.Raise 8732 ,, "Expecting property name" | |
End If | |
Idx = idx + 1 | |
Loop | |
Idx = idx + 1 | |
End Function | |
Private Function ParseArray( ByRef str, ByRef idx) | |
Dim c, values, value | |
values = CreateObject ( "Scripting.Dictionary" ) | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "]" Then | |
Idx = idx + 1 | |
ParseArray = values.Items | |
Exit Function | |
End If | |
Do | |
Idx = SkipWhitespace(str, idx) | |
value = ScanOnce(str, idx) | |
values.Add values.Count, value | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "]" Then | |
Exit Do | |
ElseIf c <> "," Then | |
Err.Raise 8732 ,, "Expecting , delimiter in Array, found " & c | |
End If | |
Idx = idx + 1 | |
Loop | |
Idx = idx + 1 | |
ParseArray = values.Items | |
End Function | |
Private Function ParseString( ByRef str, ByRef idx) | |
Dim chunks, content, terminator, ms, esc, char | |
chunks = CreateObject ( "Scripting.Dictionary" ) | |
Do | |
ms = StringChunk.Execute( Mid (str, idx)) | |
If ms.Count = 0 Then | |
Err.Raise 8732 ,, "Unterminated string starting" | |
End If | |
Content = ms( 0 ).Submatches( 0 ) | |
Terminator = ms( 0 ).Submatches( 1 ) | |
If Len (content) > 0 Then | |
chunks.Add chunks.Count, content | |
End If | |
Idx = idx + ms( 0 ).Length | |
If terminator = """" Then | |
Exit Do | |
ElseIf terminator <> "\" Then | |
Err.Raise 8732 ,, "Invalid control character" | |
End If | |
Esc = Mid( str, idx, 1 ) | |
If esc <> "u" Then | |
Select Case esc | |
Case """" char = """" | |
Case "\" char = "\" | |
Case "/" char = "/" | |
Case "b" char = b | |
Case "f" char = f | |
Case "n" char = n | |
Case "r" char = r | |
Case "t" char = t | |
Case Else Err.Raise 8732 ,, "Invalid escape" | |
End Select | |
Idx = idx + 1 | |
Else | |
Char = ChrW( "&H" & Mid (str, idx + 1 , 4 )) | |
Idx = idx + 5 | |
End If | |
chunks.Add chunks.Count, char | |
Loop | |
ParseString = Join (chunks.Items, "" ) | |
End Function | |
Private Function SkipWhitespace( ByRef str, ByVal idx) | |
Do While idx <= Len (str) And _ | |
InStr (Whitespace, Mid( str, idx, 1 )) > 0 | |
Idx = idx + 1 | |
Loop | |
SkipWhitespace = idx | |
End Function | |
End Class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Class VbsJson | |
'Author: Demon (http://demon.tw), 2012-5-3 | |
'Modified by: t3rminus, 2019-11-20 | |
'Changelog: | |
' - Fix bug relating to empty array properties in object | |
' - Encode not tested | |
Private Whitespace, NumberRegex, StringChunk | |
Private b, f, r, n, t | |
Private Sub Class_Initialize | |
Whitespace = " " & vbTab & vbCr & vbLf | |
b = ChrW( 8 ) | |
f = vbFormFeed | |
r = vbCr | |
n = vbLf | |
t = vbTab | |
Set NumberRegex = New RegExp | |
NumberRegex.Pattern = "(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?" | |
NumberRegex.Global = False | |
NumberRegex.MultiLine = True | |
NumberRegex.IgnoreCase = True | |
Set StringChunk = New RegExp | |
StringChunk.Pattern = "([\s\S]*?)([""\\\x00-\x1f])" | |
StringChunk.Global = False | |
StringChunk.MultiLine = True | |
StringChunk.IgnoreCase = True | |
End Sub | |
'Return a JSON string representation of a VBScript data structure | |
'Supports the following objects and types | |
'+------------+--------+ | |
'| VBScript | JSON | | |
'+------------+--------+ | |
'| Dictionary | object | | |
'| Array | array | | |
'| String | string | | |
'| Number | number | | |
'| True | true | | |
'| False | false | | |
'| Null | null | | |
'+------------+--------+ | |
Public Function Encode( ByRef obj) | |
Dim buf, i, c, g | |
Set buf = CreateObject ( "Scripting.Dictionary" ) | |
Select Case VarType (obj) | |
Case vbNull | |
buf.Add buf.Count, "null" | |
Case vbBoolean | |
If obj Then | |
buf.Add buf.Count, "true" | |
Else | |
buf.Add buf.Count, "false" | |
End If | |
Case vbInteger , vbLong , vbSingle , vbDouble | |
buf.Add buf.Count, obj | |
Case vbString | |
buf.Add buf.Count, """" | |
For i = 1 To Len (obj) | |
c = Mid( obj, i, 1 ) | |
Select Case c | |
Case """" buf.Add buf.Count, "\""" | |
Case "\" buf.Add buf.Count, "\\" | |
Case "/" buf.Add buf.Count, "/" | |
Case b buf.Add buf.Count, "\b" | |
Case f buf.Add buf.Count, "\f" | |
Case r buf.Add buf.Count, "\r" | |
Case n buf.Add buf.Count, "\n" | |
Case t buf.Add buf.Count, "\t" | |
Case Else | |
If AscW(c) >= 0 And AscW(c) <= 31 Then | |
c = Right ( "0" & Hex (AscW(c)), 2 ) | |
buf.Add buf.Count, "\u00" & c | |
Else | |
buf.Add buf.Count, c | |
End If | |
End Select | |
Next | |
buf.Add buf.Count, """" | |
Case vbArray + vbVariant | |
g = True | |
buf.Add buf.Count, "[" | |
For Each i In obj | |
If g Then g = False Else buf.Add buf.Count, "," | |
buf.Add buf.Count, Encode(i) | |
Next | |
buf.Add buf.Count, "]" | |
Case vbObject | |
If TypeName (obj) = "Dictionary" Then | |
g = True | |
buf.Add buf.Count, "{" | |
For Each i In obj | |
If g Then g = False Else buf.Add buf.Count, "," | |
buf.Add buf.Count, """" & i & """" & ":" & Encode(obj(i)) | |
Next | |
buf.Add buf.Count, "}" | |
Else | |
Err.Raise 8732 ,, "None dictionary object" | |
End If | |
Case Else | |
buf.Add buf.Count, """" & CStr (obj) & """" | |
End Select | |
Encode = Join (buf.Items, "" ) | |
End Function | |
'Return the VBScript representation of ``str(`` | |
'Performs the following translations in decoding | |
'+--------+------------+ | |
'| JSON | VBScript | | |
'+--------+------------+ | |
'| object | Dictionary | | |
'| array | Array | | |
'| string | String | | |
'| number | Number | | |
'| true | True | | |
'| false | False | | |
'| null | Null | | |
'+--------+------------+ | |
Public Function Decode( ByRef str) | |
Dim idx | |
Idx = SkipWhitespace(str, 1 ) | |
If Mid( str, idx, 1 ) = "{" Then | |
Set Decode = ScanOnce(str, 1 ) | |
Else | |
Decode = ScanOnce(str, 1 ) | |
End If | |
End Function | |
Private Function ScanOnce( ByRef str, ByRef idx) | |
Dim c, ms | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "{" Then | |
Idx = idx + 1 | |
Set ScanOnce = ParseObject(str, idx) | |
Exit Function | |
ElseIf c = "[" Then | |
Idx = idx + 1 | |
ScanOnce = ParseArray(str, idx) | |
Exit Function | |
ElseIf c = """" Then | |
Idx = idx + 1 | |
ScanOnce = ParseString(str, idx) | |
Exit Function | |
ElseIf c = "n" And StrComp ( "null" , Mid (str, idx, 4 )) = 0 Then | |
Idx = idx + 4 | |
ScanOnce = Null | |
Exit Function | |
ElseIf c = "t" And StrComp ( "true" , Mid (str, idx, 4 )) = 0 Then | |
Idx = idx + 4 | |
ScanOnce = True | |
Exit Function | |
ElseIf c = "f" And StrComp ( "false" , Mid (str, idx, 5 )) = 0 Then | |
Idx = idx + 5 | |
ScanOnce = False | |
Exit Function | |
End If | |
Set ms = NumberRegex.Execute( Mid (str, idx)) | |
If ms.Count = 1 Then | |
Idx = idx + ms( 0 ).Length | |
ScanOnce = CDbl (ms( 0 )) | |
Exit Function | |
End If | |
Err.Raise 8732 ,, "No JSON object could be ScanOnced" | |
End Function | |
Private Function ParseObject( ByRef str, ByRef idx) | |
Dim c, key, value | |
Set ParseObject = CreateObject ( "Scripting.Dictionary" ) | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "}" Then | |
Idx = idx + 1 | |
Exit Function | |
ElseIf c <> """" Then | |
Err.Raise 8732 ,, "Expecting property name" | |
End If | |
Idx = idx + 1 | |
Do | |
Key = ParseString(str, idx) | |
Idx = SkipWhitespace(str, idx) | |
If Mid( str, idx, 1 ) <> ":" Then | |
Err.Raise 8732 ,, "Expecting : delimiter" | |
End If | |
Idx = SkipWhitespace(str, idx + 1 ) | |
If Mid( str, idx, 1 ) = "{" Then | |
Set value = ScanOnce(str, idx) | |
Else | |
Value = ScanOnce(str, idx) | |
End If | |
ParseObject.Add key, value | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "}" Then | |
Exit Do | |
ElseIf c <> "," Then | |
Err.Raise 8732 ,, "Expecting , delimiter in Object, found " & c | |
End If | |
Idx = SkipWhitespace(str, idx + 1 ) | |
c = Mid( str, idx, 1 ) | |
If c <> """" Then | |
Err.Raise 8732 ,, "Expecting property name" | |
End If | |
Idx = idx + 1 | |
Loop | |
Idx = idx + 1 | |
End Function | |
Private Function ParseArray( ByRef str, ByRef idx) | |
Dim c, values, value | |
Set values = CreateObject ( "Scripting.Dictionary" ) | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "]" Then | |
Idx = idx + 1 | |
ParseArray = values.Items | |
Exit Function | |
End If | |
Do | |
Idx = SkipWhitespace(str, idx) | |
If Mid( str, idx, 1 ) = "{" Then | |
Set value = ScanOnce(str, idx) | |
Else | |
Value = ScanOnce(str, idx) | |
End If | |
values.Add values.Count, value | |
Idx = SkipWhitespace(str, idx) | |
c = Mid( str, idx, 1 ) | |
If c = "]" Then | |
Exit Do | |
ElseIf c <> "," Then | |
Err.Raise 8732 ,, "Expecting , delimiter in Array, found " & c | |
End If | |
Idx = idx + 1 | |
Loop | |
Idx = idx + 1 | |
ParseArray = values.Items | |
End Function | |
Private Function ParseString( ByRef str, ByRef idx) | |
Dim chunks, content, terminator, ms, esc, char | |
Set chunks = CreateObject ( "Scripting.Dictionary" ) | |
Do | |
Set ms = StringChunk.Execute( Mid (str, idx)) | |
If ms.Count = 0 Then | |
Err.Raise 8732 ,, "Unterminated string starting" | |
End If | |
Content = ms( 0 ).Submatches( 0 ) | |
Terminator = ms( 0 ).Submatches( 1 ) | |
If Len (content) > 0 Then | |
chunks.Add chunks.Count, content | |
End If | |
Idx = idx + ms( 0 ).Length | |
If terminator = """" Then | |
Exit Do | |
ElseIf terminator <> "\" Then | |
Err.Raise 8732 ,, "Invalid control character" | |
End If | |
Esc = Mid( str, idx, 1 ) | |
If esc <> "u" Then | |
Select Case esc | |
Case """" char = """" | |
Case "\" char = "\" | |
Case "/" char = "/" | |
Case "b" char = b | |
Case "f" char = f | |
Case "n" char = n | |
Case "r" char = r | |
Case "t" char = t | |
Case Else Err.Raise 8732 ,, "Invalid escape" | |
End Select | |
Idx = idx + 1 | |
Else | |
Char = ChrW( "&H" & Mid (str, idx + 1 , 4 )) | |
Idx = idx + 5 | |
End If | |
chunks.Add chunks.Count, char | |
Loop | |
ParseString = Join (chunks.Items, "" ) | |
End Function | |
Private Function SkipWhitespace( ByRef str, ByVal idx) | |
Do While idx <= Len (str) And _ | |
InStr (Whitespace, Mid( str, idx, 1 )) > 0 | |
Idx = idx + 1 | |
Loop | |
SkipWhitespace = idx | |
End Function | |
End Class |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment