Разобрать JSON в scripting.dictionary и передать через COM в VBA - PullRequest
0 голосов
/ 17 марта 2019

Справочная информация. В настоящее время я использую VBA-JSON для анализа строк json в словарных объектах в VBA (Access). Это довольно медленно, процесс выборки занимает 18 секунд.

В VB.NET метод десериализации JavaScriptSerializer занимает 0,5 секунды для тех же данных.

Я хочу, чтобы производительность метода VB.NET была доступна для моего кода VBA через COM Interop. Но COM не может передавать универсальные объекты, и хотя я читал, что решение включает в себя маршалинг, у меня возникают проблемы с пониманием этой опции.

Я могу успешно передать тип scripting.dictionary из моего класса COM VB.NET, когда я генерирую его вручную и могу использовать его в 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 возвращает тип IDictionary, а не scripting.dictionary.

Так что я должен либо найти способ десериализации json в scripting.dictionary, либо преобразовать IDictionary в scripting.dictionary.

Как я могу это сделать?
Учитывая мою общую цель, есть ли предложения по альтернативным методам?

Edit.

В проекте используется собственный 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

Ответ Эрика А. показал мне, как можно выполнять синтаксический анализ по запросу или «потоковую передачу». Разбор выполняется только тогда, когда вы запрашиваете элемент.

Я думаю, что неправильно понял природу того, как VB.NET десериализует JSON. Это должно быть то же самое. Поэтому, когда я увидел 500 мс вместо 18 секунд, я не знал, на что смотрю. Я подозреваю, что если бы я прошелся по десериализованному json в VB.NET и проверил каждый элемент, это заняло бы намного больше времени. Мой пример данных для тестирования производительности на самом деле был набором из 124 примеров JSON, некоторые с гораздо большим количеством ITEMS, следовательно, с 18 секундами. Это правильно?

Ответ Альберта показал мне кое-что, что я хотел сделать изначально, но не смог заставить его работать. Отличный полный ответ, я буду копаться в следующем.

Ответы [ 2 ]

1 голос
/ 18 марта 2019

Почему бы просто не использовать .net dict? Скорее всего, вам когда-нибудь понадобится только счет и установить или потянуть за ключ.

Итак, этот код должен нормально работать:

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-взаимодействия, и вы отправитесь на гонки. Я использовал 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

Обратите внимание, что даже inteli-sense в редакторе VBA будет работать выше. Так что просто предоставьте несколько дополнительных методов для работы с .net dict, и вам даже не придется возиться с библиотекой сценариев в VBA - и эту библиотеку все равно трудно использовать в любом случае. Таким образом, вы можете избавиться от одной ссылки на библиотеку, просто используя объект .net dict, как я делал выше.

Редактировать

Теперь, когда пользователь предоставил образец json?

Для работы с заданными данными выполняются следующие шаги.

Создайте новый пустой класс в .net ctrl-a, del key. С этим пустым классом у нас есть образец json в текстовом документе. ctrl-a, ctrl-c. Теперь в Visual Studio (VS) отредактируйте-> вставьте специальный, PASTE AS JSON Class.

На этом этапе ваши классы автоматически генерируются для вас. Из-за ограничений парсера Neutonsoft он не поддерживает массивы (). (так грустно, я мог бы добавить).

Итак, выполните поиск (), и вы найдете массивы и замените их.

So

Public Property taxes() As Tax

Становится

Public Property taxes As IList(Of Tax)

Мы используем iList вместо List, потому что здесь нужна возможность чтения / записи. И, как я уже сказал, мы ТОЛЬКО выполняем эту работу, потому что NeutonSoft simple не любит массивы.

Хорошо, есть примерно 4 из вышеперечисленного. На это уходит менее 1 минуты вашего времени.

Тем не менее, нужно пересмотреть все вышесказанное, если вы хотите ДЕЙСТВИТЕЛЬНО хороший интеллект в VBA. Итак, давайте добавим несколько классов для этих списков.

Опять же, их всего около 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 минут мы теперь можем просматривать данные в intel-sense в VBA.

И нам нужен счетчик (хотя iList отображается в VBA, он по какой-то причине не выставляет счетчик). Я открыт для других предложений, но давайте просто добавим это сразу после:

Public Function salesTaxesNCout() As Integer

    Return salesTaxes.Count

End Function

Сделав вышеуказанные изменения? Мы все еще под 5 минут времени. Если вы использовали вставку как функцию json в VS, то чем больше вы делаете выше, тем лучше вы делаете эти изменения. Вы найдете, как уже отмечалось, менее 5 минут времени.

Опять же, из-за того, что в VBA не хватает интеллекта, вставляйте его перед каждым занятием

<ClassInterface(ClassInterfaceType.AutoDual)>

Теперь это быстрая вставка снова и снова. Вот как будет выглядеть код (фрагмент кода из нашего класса).

<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

Выше приведен только короткий отрывок. Опять же, это идет очень быстро.

Хорошо, мы закончили!

Наш основной класс теперь выглядит так:

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

Я должен отметить, что интеллектуальный смысл работает до упора.

Теперь я полагаю, что реальный вопрос заключается в том, что если у кого-нибудь есть идея, как заставить Newtonsoft работать с массивами, то мы бы сделали это за 2 минуты, вместо 5 минут, которые у меня ушли.

На самом деле, я собирался задать этот вопрос около 1 года, и я сделаю это позже сегодня. И выше я переименовал rootobject в MyCust.

0 голосов
/ 18 марта 2019

Я лично работал над быстрым и гибким интерпретатором JSON для VBA, который мог бы удовлетворить ваши потребности.

Он состоит из двух разных объектов: clsStringBuilder (очень простой построитель строк) и JSONInterpreter (основной объект, который позволяет работать с JSON).

Вы можете найти проект здесь .Обратите внимание, что я быстро загрузил его для этого вопроса, на данный момент его очень мало (документация, тестирование и т. Д.).

Пример кода:

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, найденный в примере реализации, может помочь в этом.

Быстрый тест производительности для документа JSON, которым вы поделились, показывает, что перемещение всего документа (при сохранении в ячейке рабочего листа в Excel) всловарь занимает от 0,08 до 0,11 секунды в моей системе (что в лучшем случае является низким уровнем).Конечно, при перемещении только части документа в словарь может быть выигрыш в производительности, особенно если вы можете использовать позицию (вместо имени ключа), чтобы указать, какую часть вы хотите.

...