将 JSON 解析为 scripting.dictionary 并通过 COM 传递给 VBA
Parse JSON to scripting.dictionary and pass via COM to VBA
背景:我目前正在使用 VBA-JSON 将 json 字符串解析为 VBA (Access) 中的字典对象。这很慢,一个示例过程需要 18 秒。
在 VB.NET 中,JavaScriptSerializer Deserialize 方法对相同的数据需要 0.5 秒。
我希望通过 COM Interop 为我的 VBA 代码提供 VB.NET 方法的性能。但是 COM 不能传递通用对象,虽然我读到解决方案涉及封送处理,但我无法理解该选项。
当我手动生成 scripting.dictionary 类型时,我可以从我的 VB.NET COM class 成功传递它,并且可以在 VBA.
中使用它
Public Function GetData2() As Scripting.Dictionary
Dim dict As New Scripting.Dictionary
dict.Add("a", "Athens")
dict.Add("b", "Belgrade")
Return dict
End Function
但是 JavaScriptSerializer Deserialize 方法 returns 是 IDictionary 类型,而不是 scripting.dictionary。
所以我必须找到一种方法将 json 反序列化为 scripting.dictionary 或将 IDictionary 转换为 scripting.dictionary.
我该怎么做?
鉴于我的总体目标,是否有关于替代方法的建议?
编辑。
该项目使用专有的 REST API 作为会计系统。我想创建通用工具来简化和加速各种不同的任务,来自包括 Access、Excel、vbscript 等在内的环境。API 设计师想要的一切,但从通常对 REST 不友好的工具 API编程。
用途包括从系统读取和写入数据,或将数据加载到另一个数据库,在 excel 中创建自定义报告,导入订单等。
这是销售订单的一些 JSON。
{
"id": 7,
"orderNo": "0000102692",
"division": "000",
"location": "",
"profitCenter": "",
"invoiceNo": "",
"customer": {
"id": 1996,
"code": "ER118",
"customerNo": "ER118",
"name": "E R Partridge Inc"
},
"currency": null,
"status": "O",
"type": "O",
"hold": false,
"orderDate": "2015-02-13",
"invoiceDate": null,
"requiredDate": "2015-02-13",
"address": {
"id": 2045,
"type": "B",
"linkTable": "SORD",
"linkNo": "0000102692",
"shipId": "",
"name": "E R Partridge Inc",
"line1": "1531 St Jean Baptiste St",
"line2": "",
"line3": "",
"line4": "",
"city": "St Ulric",
"postalCode": "G0J 3H0",
"provState": "QC",
"country": "CAN",
"phone": {
"number": "4187370284",
"format": 1
},
"fax": {
"number": "",
"format": 1
},
"email": "van@erpart.com",
"website": "",
"shipCode": "",
"shipDescription": "",
"salesperson": {
"code": "",
"name": ""
},
"territory": {
"code": "",
"description": ""
},
"sellLevel": 1,
"glAccount": "41100",
"defaultWarehouse": "VA",
"created": "2014-08-26T11:44:57.930000",
"modified": "2015-02-16T09:30:08",
"contacts": [
{
"name": "Van Coon",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
}
],
"salesTaxes": [
{
"code": 1,
"exempt": ""
},
{
"code": 2,
"exempt": ""
},
{
"code": 0,
"exempt": ""
},
{
"code": 0,
"exempt": ""
}
]
},
"shippingAddress": {
"id": 2044,
"type": "S",
"linkTable": "SORD",
"linkNo": "SORD0000102692 S",
"shipId": "",
"name": "E R Partridge Inc",
"line1": "1531 St Jean Baptiste St",
"line2": "",
"line3": "",
"line4": "",
"city": "St Ulric",
"postalCode": "G0J 3H0",
"provState": "QC",
"country": "CAN",
"phone": {
"number": "4187370284",
"format": 1
},
"fax": {
"number": "",
"format": 1
},
"email": "",
"website": "",
"shipCode": "",
"shipDescription": "",
"salesperson": {
"code": "",
"name": ""
},
"territory": {
"code": "",
"description": ""
},
"sellLevel": 1,
"glAccount": "41100",
"defaultWarehouse": "VA",
"created": "2014-08-26T11:44:57.930000",
"modified": "2014-08-26T11:44:57.930000",
"contacts": [
{
"name": "Van Coon",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
}
],
"salesTaxes": [
{
"code": 1,
"exempt": ""
},
{
"code": 2,
"exempt": ""
},
{
"code": 0,
"exempt": ""
},
{
"code": 0,
"exempt": ""
}
]
},
"contact": {
"name": "",
"email": "",
"phone": {
"number": "",
"format": 0
},
"fax": {
"number": "",
"format": 0
}
},
"customerPO": "",
"batchNo": 0,
"fob": "Your dock",
"referenceNo": "",
"shippingCarrier": "",
"shipDate": null,
"trackingNo": "",
"termsCode": "",
"termsText": "",
"freight": "41.95",
"taxes": [
{
"code": 1,
"name": "G.S.T.",
"shortName": "G.S.T.",
"rate": "5",
"exemptNo": "",
"total": "44.05"
},
{
"code": 2,
"name": "P.S.T.",
"shortName": "BC P.S.T.",
"rate": "7",
"exemptNo": "",
"total": "61.67"
},
{
"code": 0,
"name": "",
"shortName": "",
"rate": "0",
"exemptNo": "",
"total": 0
},
{
"code": 0,
"name": "",
"shortName": "",
"rate": "0",
"exemptNo": "",
"total": 0
}
],
"subtotal": "839",
"subtotalOrdered": "839",
"discount": "0",
"totalDiscount": "0",
"total": "986.67",
"totalOrdered": "986.67",
"grossProfit": "346.26",
"items": [
{
"id": 8,
"orderNo": "0000102692",
"sequence": 1,
"inventory": {
"id": 40,
"whse": "VA",
"partNo": "INSDB30",
"description": "InSpire Dumbbell 30"
},
"serials": null,
"whse": "VA",
"partNo": "INSDB30",
"description": "InSpire Dumbbell 30",
"comment": "",
"orderQty": "4",
"committedQty": "4",
"backorderQty": "0",
"retailPrice": "70",
"unitPrice": "70",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "280",
"extendedPriceCommitted": "280",
"suppress": false
},
{
"id": 9,
"orderNo": "0000102692",
"sequence": 2,
"inventory": {
"id": 27,
"whse": "VA",
"partNo": "NATACCBAL",
"description": "National Accupressure Balls"
},
"serials": null,
"whse": "VA",
"partNo": "NATACCBAL",
"description": "National Accupressure Balls",
"comment": "",
"orderQty": "5",
"committedQty": "5",
"backorderQty": "0",
"retailPrice": "22",
"unitPrice": "22",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "NATPRO",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "110",
"extendedPriceCommitted": "110",
"suppress": false
},
{
"id": 10,
"orderNo": "0000102692",
"sequence": 3,
"inventory": {
"id": 33,
"whse": "VA",
"partNo": "SPAB",
"description": "Springfield Ab Toner"
},
"serials": null,
"whse": "VA",
"partNo": "SPAB",
"description": "Springfield Ab Toner",
"comment": "",
"orderQty": "1",
"committedQty": "1",
"backorderQty": "0",
"retailPrice": "45",
"unitPrice": "45",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "SPRFIT",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "45",
"extendedPriceCommitted": "45",
"suppress": false
},
{
"id": 11,
"orderNo": "0000102692",
"sequence": 4,
"inventory": {
"id": 46,
"whse": "VA",
"partNo": "INSDB50",
"description": "InSpire Dumbbell 50"
},
"serials": null,
"whse": "VA",
"partNo": "INSDB50",
"description": "InSpire Dumbbell 50",
"comment": "",
"orderQty": "2",
"committedQty": "2",
"backorderQty": "0",
"retailPrice": "118",
"unitPrice": "118",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "236",
"extendedPriceCommitted": "236",
"suppress": false
},
{
"id": 12,
"orderNo": "0000102692",
"sequence": 5,
"inventory": {
"id": 42,
"whse": "VA",
"partNo": "INSDB15",
"description": "InSpire Dumbbell 15"
},
"serials": null,
"whse": "VA",
"partNo": "INSDB15",
"description": "InSpire Dumbbell 15",
"comment": "",
"orderQty": "3",
"committedQty": "3",
"backorderQty": "0",
"retailPrice": "34",
"unitPrice": "34",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "102",
"extendedPriceCommitted": "102",
"suppress": false
},
{
"id": 13,
"orderNo": "0000102692",
"sequence": 6,
"inventory": {
"id": 9,
"whse": "VA",
"partNo": "INSWP50",
"description": "InSpire Weight Plate 50"
},
"serials": null,
"whse": "VA",
"partNo": "INSWP50",
"description": "InSpire Weight Plate 50",
"comment": "",
"orderQty": "1",
"committedQty": "1",
"backorderQty": "0",
"retailPrice": "66",
"unitPrice": "66",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "66",
"extendedPriceCommitted": "66",
"suppress": false
}
],
"payments": [
],
"createdBy": "SS",
"modifiedBy": "SS",
"created": "2014-08-26T11:44:57.930000",
"modified": "2015-02-20T08:09:55",
"links": {
"notes": "https://localhost:10880/api/v2/companies/INSPIRE/sales/orders/7/notes/"
}
}
编辑 2
Erik A 的回答向我展示了如何按需或 'streaming' 完成解析。解析仅在您请求元素时完成。
我想我误解了 VB.NET 如何反序列化 JSON 的本质。它一定在做同样的事情。所以当我看到 500 毫秒而不是 18 秒时,我不知道我在看什么。我怀疑如果我遍历 VB.NET 中的反序列化 json 并检查每个元素,则需要更长的时间。我用于性能测试的样本数据实际上是 JSON 样本中的 124 个样本的集合,其中一些具有更多 ITEMS,因此是 18 秒。 这是正确的吗?
Albert 的回答向我展示了一些我原本想做但无法实现的事情。很好的完整答案,我将在接下来进行深入研究。
为什么不直接使用 .net 字典?您可能只需要计数、设置或按键拉动。
所以,这段代码应该没问题:
Imports System.Runtime.InteropServices
Imports Newtonsoft.Json
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class MyJSON
Private m_DICT As New Dictionary(Of String, String)
Public Function ToJson() As String
Dim s As String = ""
s = JsonConvert.SerializeObject(m_DICT)
Return s
End Function
Public Sub JsonToDict(s As String)
m_DICT = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(s)
End Sub
Public Sub Add(sKey As String, sValue As String)
m_DICT.Add(sKey, sValue)
End Sub
Public Function ix(s As String) As String
Return m_DICT(s)
End Function
Public Function Count() As Integer
Return m_DICT.Count
End Function
End Class
只需确保将上述项目设置为 x86。选中该框以注册 com interop,然后您就可以参加比赛了。我使用了 NewtonSOFT json,但不清楚您使用的是什么序列化程序库。
所以,现在您的 VBA 代码变为:
Sub TEstMyCom()
Dim MyJSON As New TestCom2.MyJSON
MyJSON.Add "a", "aaaaa"
MyJSON.Add "b", "bbbbb"
MyJSON.Add "c", "ccccc"
Dim ss As String
ss = MyJSON.toJSON
Debug.Print MyJSON.toJSON
' convert the string to array (dict)
Dim MyJSON2 As New TestCom2.MyJSON
MyJSON2.JsonToDict ss
Debug.Print MyJSON2.ix("c")
End Sub
输出:
{"a":"aaaaa","b":"bbbbb","c":"ccccc"}
ccccc
请注意,即使 VBA 编辑器中的智能感知也适用于上述内容。因此,只需公开一些额外的方法来使用 .net 字典,您甚至不必为 VBA 中的脚本库操心——无论如何,该库使用起来有点麻烦。因此,您只需像我上面那样使用 .net dict 对象就可以摆脱一个库引用。
编辑
现在用户已经提供了样本 json?
要处理给定的数据,步骤是。
在 .net 中创建一个新空白 class ctrl-a, del 键。有了这个空 class,我们就有了文本文档中的示例 json。 ctrl-a,ctrl-c。现在在 Visual Studio (VS) 中,编辑->选择性粘贴,粘贴为 JSON Class.
此时,您的 classes 已自动为您生成。由于 Neutonsoft 解析器的限制,它不支持 arrays()。 (很伤心,我可能会补充)。
因此,搜索 (),找到数组并替换它们。
所以
Public Property taxes() As Tax
变成
Public Property taxes As IList(Of Tax)
我们使用 iList 代替 List 是因为我们需要 read/write 这里的能力。正如我所说,我们只是在做这项工作,因为 NeutonSoft simple 不喜欢数组。
好的,上面大概有4个。这需要不到 1 分钟的时间来修改。
但是,如果想在 VBA 中获得非常好的智能感知,则需要重新公开上述内容。因此,让我们为这些 ilist 添加一些 classes。
同样,只有大约 4 个。
所以我们有这个:
Public Property salesTaxes As IList(Of Salestax)
Public Property salesTaxesN(ix As Integer) As Salestax
Get
Return salesTaxes(ix)
End Get
Set(s As Salestax)
salesTaxes(ix) = s
End Set
End Property
以上内容会给我们很好的情报。我们不必执行上述操作,但多出来的 2 分钟,我们现在可以在 VBA.
中以 intel-sense 方式遍历数据
而且我们需要一个计数(虽然 iList 看起来 VBA,但出于某种原因它没有公开计数)。我愿意接受其他建议,但让我们在上面添加这个:
Public Function salesTaxesNCout() As Integer
Return salesTaxes.Count
End Function
是否做了以上修改?我们还不到 5 分钟的时间。如果您在 VS 中使用粘贴为 json 功能,那么执行上述操作的次数越多,您就越能更好地进行这些更改。如前所述,您会发现不到 5 分钟的时间。
同样由于 VBA 中需要智能感知,请在每个 class
之前粘贴
<ClassInterface(ClassInterfaceType.AutoDual)>
现在这是一次又一次的快速粘贴。所以这就是代码的样子(来自我们的 class 的示例片段)。
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Salesperson
Public Property code As String
Public Property name As String
End Class
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Territory
Public Property code As String
Public Property description As String
End Class
以上只是一个简短的片段。同样,这真的很快。
好的,我们完成了!
我们的主要 class 现在看起来像这样:
imports System.Runtime.InteropServices
imports Newtonsoft.Json
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class MyJSON
Public MyCust As New Jcust
Public Function ToJson() As String
Dim s As String = ""
s = JsonConvert.SerializeObject(MyCust)
Return s
End Function
Public Sub JsonToCust(s As String)
MyCust = JsonConvert.DeserializeObject(Of Jcust)(s)
End Sub
End Class
就是这样!你现在有了一个很好的工作设置。
我们上面的 VBA 代码现在是这样的:
Sub custTest()
Dim strJSON As String
Dim intF As Integer
Dim strF As String
strF = "c:\test2\cust.txt"
' read in that file
intF = FreeFile()
Open strF For Input As #intF
strJSON = input(LOF(intF), intF)
Close intF
Dim cCust As New MyJSON
cCust.JsonToCust strJSON
Debug.Print cCust.MyCust.Address.salesTaxesN(1).Code
Debug.Print cCust.MyCust.Address.City
End Sub
运行以上?
输出:
2
St Ulric
我应该指出,intel-sense 一直有效。
现在我想真正的问题是,如果有人知道如何让 Newtonsoft 使用 Arrays,那么我们将在 2 分钟内完成这件事,而不是这花了我 5 分钟。
事实上,我已经想问这个问题大约 1 年了,今天晚些时候我会的。在上面我确实将 rootobject 重命名为 MyCust。
我个人一直在为 VBA 开发一种快速、灵活的 JSON 解释器,它可能会满足您的需求。
它由两个不同的对象组成:clsStringBuilder
(一个非常基本的字符串生成器)和 JSONInterpreter
(允许您使用 JSON 的主要对象)。
您可以找到项目here。请注意,我已经为这个问题快速上传了它,目前它缺少很多东西(文档、测试等)。
示例代码:
Dim jsi As New JSONInterpreter
jsi.JSON = SomeString
Debug.Print jsi.item("company", "invoice", 1, "ShipAddress", "AddressLine", 2).VBAVariant
如果您经常使用较大对象中的特定数组或对象,我建议检索 VBAVariant 属性 并进一步处理它,或者创建一个子对象。
示例:
Dim jsi As New JSONInterpreter
jsi.JSON = SomeString
Dim shipAddressJSI as JSONInterpreter
Set shipAddressJSI = jsi.item("company", "invoice", 1, "ShipAddress")
Debug.Print shipAddressJSI.item("AddressLine", 1).VBAVariant
Debug.Print shipAddressJSI.item("AddressLine", 2).VBAVariant
请注意,如果您考虑使用它,我强烈建议您先进行一些测试。示例实现中的 WalkJSON
sub 可以帮助解决这个问题。
对您共享的 JSON 文档进行的快速性能测试表明,将整个文档(当存储在 Excel 中的工作表单元格中时)移动到字典需要 0.08 到 0.11 秒在我的系统上(充其量是低端的)。仅将文档的一部分移动到字典时肯定会提高性能,特别是如果您可以使用位置(而不是键名)来指定您想要的部分。
背景:我目前正在使用 VBA-JSON 将 json 字符串解析为 VBA (Access) 中的字典对象。这很慢,一个示例过程需要 18 秒。
在 VB.NET 中,JavaScriptSerializer Deserialize 方法对相同的数据需要 0.5 秒。
我希望通过 COM Interop 为我的 VBA 代码提供 VB.NET 方法的性能。但是 COM 不能传递通用对象,虽然我读到解决方案涉及封送处理,但我无法理解该选项。
当我手动生成 scripting.dictionary 类型时,我可以从我的 VB.NET COM class 成功传递它,并且可以在 VBA.
中使用它 Public Function GetData2() As Scripting.Dictionary
Dim dict As New Scripting.Dictionary
dict.Add("a", "Athens")
dict.Add("b", "Belgrade")
Return dict
End Function
但是 JavaScriptSerializer Deserialize 方法 returns 是 IDictionary 类型,而不是 scripting.dictionary。
所以我必须找到一种方法将 json 反序列化为 scripting.dictionary 或将 IDictionary 转换为 scripting.dictionary.
我该怎么做?
鉴于我的总体目标,是否有关于替代方法的建议?
编辑。
该项目使用专有的 REST API 作为会计系统。我想创建通用工具来简化和加速各种不同的任务,来自包括 Access、Excel、vbscript 等在内的环境。API 设计师想要的一切,但从通常对 REST 不友好的工具 API编程。
用途包括从系统读取和写入数据,或将数据加载到另一个数据库,在 excel 中创建自定义报告,导入订单等。
这是销售订单的一些 JSON。
{
"id": 7,
"orderNo": "0000102692",
"division": "000",
"location": "",
"profitCenter": "",
"invoiceNo": "",
"customer": {
"id": 1996,
"code": "ER118",
"customerNo": "ER118",
"name": "E R Partridge Inc"
},
"currency": null,
"status": "O",
"type": "O",
"hold": false,
"orderDate": "2015-02-13",
"invoiceDate": null,
"requiredDate": "2015-02-13",
"address": {
"id": 2045,
"type": "B",
"linkTable": "SORD",
"linkNo": "0000102692",
"shipId": "",
"name": "E R Partridge Inc",
"line1": "1531 St Jean Baptiste St",
"line2": "",
"line3": "",
"line4": "",
"city": "St Ulric",
"postalCode": "G0J 3H0",
"provState": "QC",
"country": "CAN",
"phone": {
"number": "4187370284",
"format": 1
},
"fax": {
"number": "",
"format": 1
},
"email": "van@erpart.com",
"website": "",
"shipCode": "",
"shipDescription": "",
"salesperson": {
"code": "",
"name": ""
},
"territory": {
"code": "",
"description": ""
},
"sellLevel": 1,
"glAccount": "41100",
"defaultWarehouse": "VA",
"created": "2014-08-26T11:44:57.930000",
"modified": "2015-02-16T09:30:08",
"contacts": [
{
"name": "Van Coon",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
}
],
"salesTaxes": [
{
"code": 1,
"exempt": ""
},
{
"code": 2,
"exempt": ""
},
{
"code": 0,
"exempt": ""
},
{
"code": 0,
"exempt": ""
}
]
},
"shippingAddress": {
"id": 2044,
"type": "S",
"linkTable": "SORD",
"linkNo": "SORD0000102692 S",
"shipId": "",
"name": "E R Partridge Inc",
"line1": "1531 St Jean Baptiste St",
"line2": "",
"line3": "",
"line4": "",
"city": "St Ulric",
"postalCode": "G0J 3H0",
"provState": "QC",
"country": "CAN",
"phone": {
"number": "4187370284",
"format": 1
},
"fax": {
"number": "",
"format": 1
},
"email": "",
"website": "",
"shipCode": "",
"shipDescription": "",
"salesperson": {
"code": "",
"name": ""
},
"territory": {
"code": "",
"description": ""
},
"sellLevel": 1,
"glAccount": "41100",
"defaultWarehouse": "VA",
"created": "2014-08-26T11:44:57.930000",
"modified": "2014-08-26T11:44:57.930000",
"contacts": [
{
"name": "Van Coon",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
},
{
"name": "",
"email": "",
"phone": {
"number": "",
"format": 1
},
"fax": {
"number": "",
"format": 1
}
}
],
"salesTaxes": [
{
"code": 1,
"exempt": ""
},
{
"code": 2,
"exempt": ""
},
{
"code": 0,
"exempt": ""
},
{
"code": 0,
"exempt": ""
}
]
},
"contact": {
"name": "",
"email": "",
"phone": {
"number": "",
"format": 0
},
"fax": {
"number": "",
"format": 0
}
},
"customerPO": "",
"batchNo": 0,
"fob": "Your dock",
"referenceNo": "",
"shippingCarrier": "",
"shipDate": null,
"trackingNo": "",
"termsCode": "",
"termsText": "",
"freight": "41.95",
"taxes": [
{
"code": 1,
"name": "G.S.T.",
"shortName": "G.S.T.",
"rate": "5",
"exemptNo": "",
"total": "44.05"
},
{
"code": 2,
"name": "P.S.T.",
"shortName": "BC P.S.T.",
"rate": "7",
"exemptNo": "",
"total": "61.67"
},
{
"code": 0,
"name": "",
"shortName": "",
"rate": "0",
"exemptNo": "",
"total": 0
},
{
"code": 0,
"name": "",
"shortName": "",
"rate": "0",
"exemptNo": "",
"total": 0
}
],
"subtotal": "839",
"subtotalOrdered": "839",
"discount": "0",
"totalDiscount": "0",
"total": "986.67",
"totalOrdered": "986.67",
"grossProfit": "346.26",
"items": [
{
"id": 8,
"orderNo": "0000102692",
"sequence": 1,
"inventory": {
"id": 40,
"whse": "VA",
"partNo": "INSDB30",
"description": "InSpire Dumbbell 30"
},
"serials": null,
"whse": "VA",
"partNo": "INSDB30",
"description": "InSpire Dumbbell 30",
"comment": "",
"orderQty": "4",
"committedQty": "4",
"backorderQty": "0",
"retailPrice": "70",
"unitPrice": "70",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "280",
"extendedPriceCommitted": "280",
"suppress": false
},
{
"id": 9,
"orderNo": "0000102692",
"sequence": 2,
"inventory": {
"id": 27,
"whse": "VA",
"partNo": "NATACCBAL",
"description": "National Accupressure Balls"
},
"serials": null,
"whse": "VA",
"partNo": "NATACCBAL",
"description": "National Accupressure Balls",
"comment": "",
"orderQty": "5",
"committedQty": "5",
"backorderQty": "0",
"retailPrice": "22",
"unitPrice": "22",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "NATPRO",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "110",
"extendedPriceCommitted": "110",
"suppress": false
},
{
"id": 10,
"orderNo": "0000102692",
"sequence": 3,
"inventory": {
"id": 33,
"whse": "VA",
"partNo": "SPAB",
"description": "Springfield Ab Toner"
},
"serials": null,
"whse": "VA",
"partNo": "SPAB",
"description": "Springfield Ab Toner",
"comment": "",
"orderQty": "1",
"committedQty": "1",
"backorderQty": "0",
"retailPrice": "45",
"unitPrice": "45",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "SPRFIT",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "45",
"extendedPriceCommitted": "45",
"suppress": false
},
{
"id": 11,
"orderNo": "0000102692",
"sequence": 4,
"inventory": {
"id": 46,
"whse": "VA",
"partNo": "INSDB50",
"description": "InSpire Dumbbell 50"
},
"serials": null,
"whse": "VA",
"partNo": "INSDB50",
"description": "InSpire Dumbbell 50",
"comment": "",
"orderQty": "2",
"committedQty": "2",
"backorderQty": "0",
"retailPrice": "118",
"unitPrice": "118",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "236",
"extendedPriceCommitted": "236",
"suppress": false
},
{
"id": 12,
"orderNo": "0000102692",
"sequence": 5,
"inventory": {
"id": 42,
"whse": "VA",
"partNo": "INSDB15",
"description": "InSpire Dumbbell 15"
},
"serials": null,
"whse": "VA",
"partNo": "INSDB15",
"description": "InSpire Dumbbell 15",
"comment": "",
"orderQty": "3",
"committedQty": "3",
"backorderQty": "0",
"retailPrice": "34",
"unitPrice": "34",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "102",
"extendedPriceCommitted": "102",
"suppress": false
},
{
"id": 13,
"orderNo": "0000102692",
"sequence": 6,
"inventory": {
"id": 9,
"whse": "VA",
"partNo": "INSWP50",
"description": "InSpire Weight Plate 50"
},
"serials": null,
"whse": "VA",
"partNo": "INSWP50",
"description": "InSpire Weight Plate 50",
"comment": "",
"orderQty": "1",
"committedQty": "1",
"backorderQty": "0",
"retailPrice": "66",
"unitPrice": "66",
"discountable": true,
"discountPct": "0",
"discountAmt": "0",
"taxFlags": [
true,
true,
false,
false
],
"sellMeasure": "EA",
"vendor": "INSPIRE",
"levyCode": "",
"requiredDate": "2015-08-26",
"extendedPriceOrdered": "66",
"extendedPriceCommitted": "66",
"suppress": false
}
],
"payments": [
],
"createdBy": "SS",
"modifiedBy": "SS",
"created": "2014-08-26T11:44:57.930000",
"modified": "2015-02-20T08:09:55",
"links": {
"notes": "https://localhost:10880/api/v2/companies/INSPIRE/sales/orders/7/notes/"
}
}
编辑 2
Erik A 的回答向我展示了如何按需或 'streaming' 完成解析。解析仅在您请求元素时完成。
我想我误解了 VB.NET 如何反序列化 JSON 的本质。它一定在做同样的事情。所以当我看到 500 毫秒而不是 18 秒时,我不知道我在看什么。我怀疑如果我遍历 VB.NET 中的反序列化 json 并检查每个元素,则需要更长的时间。我用于性能测试的样本数据实际上是 JSON 样本中的 124 个样本的集合,其中一些具有更多 ITEMS,因此是 18 秒。 这是正确的吗?
Albert 的回答向我展示了一些我原本想做但无法实现的事情。很好的完整答案,我将在接下来进行深入研究。
为什么不直接使用 .net 字典?您可能只需要计数、设置或按键拉动。
所以,这段代码应该没问题:
Imports System.Runtime.InteropServices
Imports Newtonsoft.Json
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class MyJSON
Private m_DICT As New Dictionary(Of String, String)
Public Function ToJson() As String
Dim s As String = ""
s = JsonConvert.SerializeObject(m_DICT)
Return s
End Function
Public Sub JsonToDict(s As String)
m_DICT = JsonConvert.DeserializeObject(Of Dictionary(Of String, String))(s)
End Sub
Public Sub Add(sKey As String, sValue As String)
m_DICT.Add(sKey, sValue)
End Sub
Public Function ix(s As String) As String
Return m_DICT(s)
End Function
Public Function Count() As Integer
Return m_DICT.Count
End Function
End Class
只需确保将上述项目设置为 x86。选中该框以注册 com interop,然后您就可以参加比赛了。我使用了 NewtonSOFT json,但不清楚您使用的是什么序列化程序库。
所以,现在您的 VBA 代码变为:
Sub TEstMyCom()
Dim MyJSON As New TestCom2.MyJSON
MyJSON.Add "a", "aaaaa"
MyJSON.Add "b", "bbbbb"
MyJSON.Add "c", "ccccc"
Dim ss As String
ss = MyJSON.toJSON
Debug.Print MyJSON.toJSON
' convert the string to array (dict)
Dim MyJSON2 As New TestCom2.MyJSON
MyJSON2.JsonToDict ss
Debug.Print MyJSON2.ix("c")
End Sub
输出:
{"a":"aaaaa","b":"bbbbb","c":"ccccc"}
ccccc
请注意,即使 VBA 编辑器中的智能感知也适用于上述内容。因此,只需公开一些额外的方法来使用 .net 字典,您甚至不必为 VBA 中的脚本库操心——无论如何,该库使用起来有点麻烦。因此,您只需像我上面那样使用 .net dict 对象就可以摆脱一个库引用。
编辑
现在用户已经提供了样本 json?
要处理给定的数据,步骤是。
在 .net 中创建一个新空白 class ctrl-a, del 键。有了这个空 class,我们就有了文本文档中的示例 json。 ctrl-a,ctrl-c。现在在 Visual Studio (VS) 中,编辑->选择性粘贴,粘贴为 JSON Class.
此时,您的 classes 已自动为您生成。由于 Neutonsoft 解析器的限制,它不支持 arrays()。 (很伤心,我可能会补充)。
因此,搜索 (),找到数组并替换它们。
所以
Public Property taxes() As Tax
变成
Public Property taxes As IList(Of Tax)
我们使用 iList 代替 List 是因为我们需要 read/write 这里的能力。正如我所说,我们只是在做这项工作,因为 NeutonSoft simple 不喜欢数组。
好的,上面大概有4个。这需要不到 1 分钟的时间来修改。
但是,如果想在 VBA 中获得非常好的智能感知,则需要重新公开上述内容。因此,让我们为这些 ilist 添加一些 classes。
同样,只有大约 4 个。 所以我们有这个:
Public Property salesTaxes As IList(Of Salestax)
Public Property salesTaxesN(ix As Integer) As Salestax
Get
Return salesTaxes(ix)
End Get
Set(s As Salestax)
salesTaxes(ix) = s
End Set
End Property
以上内容会给我们很好的情报。我们不必执行上述操作,但多出来的 2 分钟,我们现在可以在 VBA.
中以 intel-sense 方式遍历数据而且我们需要一个计数(虽然 iList 看起来 VBA,但出于某种原因它没有公开计数)。我愿意接受其他建议,但让我们在上面添加这个:
Public Function salesTaxesNCout() As Integer
Return salesTaxes.Count
End Function
是否做了以上修改?我们还不到 5 分钟的时间。如果您在 VS 中使用粘贴为 json 功能,那么执行上述操作的次数越多,您就越能更好地进行这些更改。如前所述,您会发现不到 5 分钟的时间。
同样由于 VBA 中需要智能感知,请在每个 class
之前粘贴<ClassInterface(ClassInterfaceType.AutoDual)>
现在这是一次又一次的快速粘贴。所以这就是代码的样子(来自我们的 class 的示例片段)。
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Salesperson
Public Property code As String
Public Property name As String
End Class
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Territory
Public Property code As String
Public Property description As String
End Class
以上只是一个简短的片段。同样,这真的很快。
好的,我们完成了!
我们的主要 class 现在看起来像这样:
imports System.Runtime.InteropServices
imports Newtonsoft.Json
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class MyJSON
Public MyCust As New Jcust
Public Function ToJson() As String
Dim s As String = ""
s = JsonConvert.SerializeObject(MyCust)
Return s
End Function
Public Sub JsonToCust(s As String)
MyCust = JsonConvert.DeserializeObject(Of Jcust)(s)
End Sub
End Class
就是这样!你现在有了一个很好的工作设置。
我们上面的 VBA 代码现在是这样的:
Sub custTest()
Dim strJSON As String
Dim intF As Integer
Dim strF As String
strF = "c:\test2\cust.txt"
' read in that file
intF = FreeFile()
Open strF For Input As #intF
strJSON = input(LOF(intF), intF)
Close intF
Dim cCust As New MyJSON
cCust.JsonToCust strJSON
Debug.Print cCust.MyCust.Address.salesTaxesN(1).Code
Debug.Print cCust.MyCust.Address.City
End Sub
运行以上? 输出:
2
St Ulric
我应该指出,intel-sense 一直有效。
现在我想真正的问题是,如果有人知道如何让 Newtonsoft 使用 Arrays,那么我们将在 2 分钟内完成这件事,而不是这花了我 5 分钟。
事实上,我已经想问这个问题大约 1 年了,今天晚些时候我会的。在上面我确实将 rootobject 重命名为 MyCust。
我个人一直在为 VBA 开发一种快速、灵活的 JSON 解释器,它可能会满足您的需求。
它由两个不同的对象组成:clsStringBuilder
(一个非常基本的字符串生成器)和 JSONInterpreter
(允许您使用 JSON 的主要对象)。
您可以找到项目here。请注意,我已经为这个问题快速上传了它,目前它缺少很多东西(文档、测试等)。
示例代码:
Dim jsi As New JSONInterpreter
jsi.JSON = SomeString
Debug.Print jsi.item("company", "invoice", 1, "ShipAddress", "AddressLine", 2).VBAVariant
如果您经常使用较大对象中的特定数组或对象,我建议检索 VBAVariant 属性 并进一步处理它,或者创建一个子对象。
示例:
Dim jsi As New JSONInterpreter
jsi.JSON = SomeString
Dim shipAddressJSI as JSONInterpreter
Set shipAddressJSI = jsi.item("company", "invoice", 1, "ShipAddress")
Debug.Print shipAddressJSI.item("AddressLine", 1).VBAVariant
Debug.Print shipAddressJSI.item("AddressLine", 2).VBAVariant
请注意,如果您考虑使用它,我强烈建议您先进行一些测试。示例实现中的 WalkJSON
sub 可以帮助解决这个问题。
对您共享的 JSON 文档进行的快速性能测试表明,将整个文档(当存储在 Excel 中的工作表单元格中时)移动到字典需要 0.08 到 0.11 秒在我的系统上(充其量是低端的)。仅将文档的一部分移动到字典时肯定会提高性能,特别是如果您可以使用位置(而不是键名)来指定您想要的部分。