标题: 用VBS解析JSON格式数据之VbsJson类
作者: Demon
链接: https://demon.tw/my-work/vbs-json.html
版权: 本博客的所有文章,都遵守“署名-非商业性使用-相同方式共享 2.5 中国大陆”协议条款。
曾经写过一篇《用VBS解析JSON格式数据》,里面提到了用 VBS 解析 JSON 的三种方法::第一,根据算法自己写一个解析 JSON 的库;第二,使用正则表达式匹配需要的数据;第三,用 JavaScript 来解析。
文章中使用的是第三种方法,即通过 MSScriptControl.ScriptControl 组件来调用 JavaScript 来解析。“乱码”根据那篇文章写了《VBS脚本之JSON数据解析》,稍稍改进了我的方法;不过他最近发现这个组件兼容性不太好,并写了一篇《VBS脚本之JSON数据解析(二)》,里面是用正则表达式来解析 JSON 的;之后又写了一篇《VBS脚本之JSON数据解析(三)[终章]》,里面用 htmlfile 来代替 MSScriptControl.ScriptControl,增强了可移植性:
Function ParseJson(strJson) Set html = CreateObject("htmlfile") Set window = html.parentWindow window.execScript "var json = " & strJson, "JScript" Set ParseJson = window.json End Function
不得不说这个方法很巧妙,但是却不能遍历数组和对象,作为“终章”似乎还差了一点,本文就是作为终章的存在:用 Native VBScript 来解析 JSON —— VbsJson类。该类提供了两个公有方法:Encode 和 Decode,分别用来生成和解析 JSON 数据。
VbsJson.vbs
Class VbsJson 'Author: Demon 'Date: 2012/5/3 'Website: https://demon.tw 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 | Double | '+---------------+-------------------+ '| 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 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" 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 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" 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
Example.vbs
'Author: Demon 'Date: 2012/5/3 'Website: https://demon.tw Dim fso, json, str, o, i Set json = New VbsJson Set fso = WScript.CreateObject("Scripting.Filesystemobject") str = fso.OpenTextFile("json.txt").ReadAll Set o = json.Decode(str) WScript.Echo o("Image")("Width") WScript.Echo o("Image")("Height") WScript.Echo o("Image")("Title") WScript.Echo o("Image")("Thumbnail")("Url") For Each i In o("Image")("IDs") WScript.Echo i Next
json.txt
{ "Image": { "Width": 800, "Height": 600, "Title": "View from 15th Floor", "Thumbnail": { "Url": "http://www.example.com/image/481989943", "Height": 125, "Width": "100" }, "IDs": [116, 943, 234, 38793] } }
欢迎测试和反馈BUG。
赞赏微信赞赏支付宝赞赏
随机文章:
A little afterword
Here is a POC of traversing arrays and objects with ParseJson
Error handling for nulls and blanks will be needed
Set myjson = ParseJson(returnstring)
on error resume next
for i=0 to myjson.length-1
theItem=””
theChildItem=””
‘ text of an item
exestring = “theItem=myjson.[” & i & “].tcpPorts”
execute(exestring)
‘extract a child object
exestring = “set theObject=myjson.[” & i & “].urls”
execute(exestring)
‘ text of a child item
urlitem = 1 ‘ too lazy to do a for next on POC
exestring = “theChildItem=theObject.[” & urlitem & “]”
execute(exestring)
wscript.echo “for i = ” & i
wscript.echo ” tcpPorts text=” & theItem
wscript.echo ” url length=” & theObject.length
wscript.echo ” the ” & urlitem & ” url is ” & theChildItem
next
——– json sample from the MS office endpoints list ——
[
{
“id”: 1,
“serviceArea”: “Exchange”,
“serviceAreaDisplayName”: “Exchange Online”,
“urls”: [
“outlook.office.com”,
“outlook.office365.com”
],
“ips”: [
“13.107.6.152/31”,
“13.107.18.10/31”,
“13.107.128.0/22”,
“23.103.160.0/20”,
“40.96.0.0/13”,
“40.104.0.0/15”,
“52.96.0.0/14”,
“131.253.33.215/32”,
“132.245.0.0/16”,
“150.171.32.0/22”,
“204.79.197.215/32”,
“2603:1006::/40”,
“2603:1016::/36”,
“2603:1026::/36”,
“2603:1036::/36”,
“2603:1046::/36”,
“2603:1056::/36”,
“2603:1096::/38”,
“2603:1096:400::/40”,
“2603:1096:600::/40”,
“2603:1096:a00::/39”,
“2603:1096:c00::/40”,
“2603:10a6:200::/40”,
“2603:10a6:400::/40”,
“2603:10a6:600::/40”,
“2603:10a6:800::/40”,
“2603:10d6:200::/40”,
“2620:1ec:4::152/128”,
“2620:1ec:4::153/128”,
“2620:1ec:c::10/128”,
“2620:1ec:c::11/128”,
“2620:1ec:d::10/128”,
“2620:1ec:d::11/128”,
“2620:1ec:8f0::/46”,
“2620:1ec:900::/46”,
“2620:1ec:a92::152/128”,
“2620:1ec:a92::153/128”,
“2a01:111:f400::/48”
],
“tcpPorts”: “80,443”,
“expressRoute”: true,
“category”: “Optimize”,
“required”: true
},
{
“id”: 2,
“serviceArea”: “Exchange”,
“serviceAreaDisplayName”: “Exchange Online”,
“urls”: [
“smtp.office365.com”
],
“ips”: [
“13.107.6.152/31”,
“13.107.18.10/31”,
“13.107.128.0/22”,
“23.103.160.0/20”,
“40.96.0.0/13”,
“40.104.0.0/15”,
“52.96.0.0/14”,
“131.253.33.215/32”,
“132.245.0.0/16”,
“150.171.32.0/22”,
“204.79.197.215/32”,
“2603:1006::/40”,
“2603:1016::/36”,
“2603:1026::/36”,
“2603:1036::/36”,
“2603:1046::/36”,
“2603:1056::/36”,
“2603:1096::/38”,
“2603:1096:400::/40”,
“2603:1096:600::/40”,
“2603:1096:a00::/39”,
“2603:1096:c00::/40”,
“2603:10a6:200::/40”,
“2603:10a6:400::/40”,
“2603:10a6:600::/40”,
“2603:10a6:800::/40”,
“2603:10d6:200::/40”,
“2620:1ec:4::152/128”,
“2620:1ec:4::153/128”,
“2620:1ec:c::10/128”,
“2620:1ec:c::11/128”,
“2620:1ec:d::10/128”,
“2620:1ec:d::11/128”,
“2620:1ec:8f0::/46”,
“2620:1ec:900::/46”,
“2620:1ec:a92::152/128”,
“2620:1ec:a92::153/128”,
“2a01:111:f400::/48”
],
“tcpPorts”: “587”,
“expressRoute”: true,
“category”: “Allow”,
“required”: true
}
]
you can use the following code to see json dictionary after this decoder done
Sub printJsonValue(key, value, level)
dim i,m,type_
m=4
type_=TypeName(value)
If type_ = “Dictionary” Then
WScript.Echo space(level*m) & key & “: Object { “
call showAllJson(value, level+1)
WScript.Echo space(level*m) & ” } “
elseIf type_ = “Variant()” Then
WScript.Echo space(level*m) & key & “: Array(” & cstr(ubound(value)+1) & “) [ “
for i=0 to ubound(value)
call printJsonValue(cstr(i), value(i), level+1)
next
WScript.Echo space(level*m) & ” ] “
else
if isNull(value) then
WScript.Echo space(level*m) & key & ” = Null”
else
WScript.Echo space(level*m) & key & ” = ” & cstr(value)
end if
end if
end sub
Sub showAllJson(json_object, level)
Dim val_, key_, type_
For Each key_ in json_object
call printJsonValue(key_,json_object(key_),level)
Next
End Sub
sub show(json_obj)
call showAllJson(json_obj,0)
end sub
Sub printJsonValue(key, value, level)
dim i,m,type_
m=4
type_=TypeName(value)
If type_ = “Dictionary” Then
WScript.Echo space(level*m) & key & “: Object { “
call showAllJson(value, level+1)
WScript.Echo space(level*m) & ” } “
elseIf type_ = “Variant()” Then
WScript.Echo space(level*m) & key & “: Array(” & cstr(ubound(value)+1) & “) [ “
for i=0 to ubound(value)
call printJsonValue(cstr(i), value(i), level+1)
next
WScript.Echo space(level*m) & ” ] “
else
if isNull(value) then
WScript.Echo space(level*m) & key & ” = Null”
else
WScript.Echo space(level*m) & key & ” = ” & cstr(value)
end if
end if
end sub
Sub showAllJson(json_object, level)
Dim val_, key_, type_
For Each key_ in json_object
call printJsonValue(key_,json_object(key_),level)
Next
End Sub
sub show(json_obj)
call showAllJson(json_obj,0)
end sub
also this works incorrect for case when array is empty, as example “value”:[]
you need to add increment to index in function parseArray
If c = “]” Then
>>>> idx = idx + 1
ParseArray = values.Items
Exit Function
End If
Roman回复正解。
Hi Roman, Thank you for the beautiful script, how to access a json object, something like below
{
“employees”:[
{“firstName”:”John”, “lastName”:”Doe”},
{“firstName”:”Anna”, “lastName”:”Smith”},
{“firstName”:”Peter”,”lastName”:”Jones”}
]
}
{
“employees”:[
{“firstName”:”John”, “lastName”:”Doe”},
{“firstName”:”Anna”, “lastName”:”Smith”},
{“firstName”:”Peter”,”lastName”:”Jones”}
]
}
Hi Roman,
Thank you for the wonderful dictionay, i am unware how to parse the below json object.
{
“employees”:[
{“firstName”:”John”, “lastName”:”Doe”},
{“firstName”:”Anna”, “lastName”:”Smith”},
{“firstName”:”Peter”,”lastName”:”Jones”}
]
}
{
“employees”:[
{“firstName”:”John”, “lastName”:”Doe”},
{“firstName”:”Anna”, “lastName”:”Smith”},
{“firstName”:”Peter”,”lastName”:”Jones”}
]
}
Hallo Roman, thank you for the great class, how to interpret the above json object
我在使用您的脚本处理一个没有名称的数组,类似 【{“aaa”:哈哈哈},{“aaa”:嘿嘿嘿}】,怎么调用,才能逐个返回 哈哈哈,嘿嘿嘿?Result_ID=Result(1)(“aaa”)不行,要没有好的办法,我只好逐个检索{},然后局部调用了。麻烦您指点一下。
您好,
首先感谢您的这个代码,我是VBS小白,对我帮助很大。
我想请问如何使用, 来改变已有的json的内容, 我尝试做但不成功。
谢谢!
KG