Использование VBA и VBA-JSON для доступа к данным JSON из Wordpress API

Я создаю приложение VBA, которое создает и изменяет страницы веб-сайта Wordpress, используя ресурсы, извлеченные из Интернета. API Wordpress возвращает файл JSON, но нет встроенной поддержки анализа JSON в VBA, поэтому я импортировал VBA-JSON из GitHub. Вот подпрограмма:

Sub Wordpress()

    '
    ' Wordpress API Test
    '
    Dim wpResp As Variant
    Dim sourceSheet As String
    Dim resourceURL As String
    sourceSheet = "Resources"
    resourceURL = Sheets(sourceSheet).Cells(6, 1)
    wpResp = getJSON(resourceURL + "/wp-json/wp/v2/posts")

End Sub

И функция, которую он вызывает.

Function getJSON(link) As Object

    Dim response As String
    Dim json As Object
    On Error GoTo recovery
    Dim retryCount As Integer
    retryCount = 0
    Dim web As MSXML2.XMLHTTP60
    Set web = New MSXML2.XMLHTTP60

the_start:

    web.Open "GET", link, False, UserName, pw
    web.setRequestHeader "Content-type", "application/json"
    web.send
    response = web.responseText
    While web.readyState <> 4
        DoEvents
    Wend

    On Error GoTo 0

    Debug.Print link
    Debug.Print web.Status; "XMLHTTP status "; web.statusText; " at "; Time

    Set json = JsonConverter.ParseJson(response)

    'getJSON = json ' this line produces Object variable or With block variable not set error but I can deal with it later

    Exit Function

recovery:

    retryCount = retryCount + 1
    Debug.Print "Error number: " & Err.Number & " " & Err.Description & " Retry " & retryCount
    Application.StatusBar = "Error number: " & Err.Number & " " & Err.Description & " Retry " & retryCount
    If retryCount < 4 Then GoTo the_start Else Exit Function
End Function

Этот код возвращает объект/коллекцию с 1 элементом, который содержит вариант/объект/словарь с 24 элементами, но я не знаю, как получить доступ к этим элементам. Вот скриншот:

введите здесь описание изображения

Если я использую непосредственное окно для запроса ?json.count, я получаю правильный результат «1», но примерно через шесть часов исследований в Интернете и перебора всех вариантов, которые я мог найти, я все еще застрял на том, как получить доступ к другое 24.

Вот JSON:

[{"id":1,"date":"2018-06-22T18:13:00","date_gmt":"2018-06-22T22:13:00","guid":{"rendered":"http:\/\/mytestsite.org\/?p=1"},"modified":"2018-06-22T18:13:00","modified_gmt":"2018-06-22T22:13:00","slug":"hello-world","status":"publish","type":"post","link":"http:\/\/mytestsite.org\/hello-world\/","title":{"rendered":"Blog Post Title"},"content":{"rendered":"<p>What goes into a blog post? Helpful, industry-specific content that: 1) gives readers a useful takeaway, and 2) shows you&#8217;re an industry expert. <\/p>\n<p>Use your company&#8217;s blog posts to opine on current industry topics, humanize your company, and show how your products and services can help people.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What goes into a blog post? Helpful, industry-specific content that: 1) gives readers a useful takeaway, and 2) shows you&#8217;re&hellip;<\/p>\n","protected":false},"author":1,"featured_media":212,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/posts\/1"}],"collection":[{"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/comments?post=1"}],"version-history":[{"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/posts\/1\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/media\/212"}],"wp:attachment":[{"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/media?parent=1"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/categories?post=1"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/mytestsite.org\/wp-json\/wp\/v2\/tags?post=1"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}]

В конце концов, я хочу иметь возможность раскрутить несколько сотен страниц контента WP, извлеченного и сопоставленного из нескольких интернет-источников, и поддерживать их в актуальном состоянии с помощью этого приложения. Дальнейшие предложения помимо проблемы здесь также будут полезны, если мы не выходим за пределы VBA.


person Jerome    schedule 01.07.2018    source источник
comment
Пожалуйста, поделитесь рабочим URL-адресом или опубликуйте образец JSON.   -  person omegastripes    schedule 01.07.2018
comment
Может я что-то упускаю, но попробуйте ?JSON.items(1).items(1) в ближайшем окне или если вы заранее знаете ключи словаря по имени ?JSON("PutCollectionKeyNameHere")("PutDictionaryKeyNameHere")   -  person chillin    schedule 01.07.2018
comment
Кроме того, я думаю, что прямо над вашей функцией выхода getJSON = JSON должно быть Set getJSON = json, поскольку вы назначаете объект.   -  person chillin    schedule 01.07.2018
comment
Вот текст ответа JSON   -  person Jerome    schedule 01.07.2018
comment
chillin, Set getJSON = json работает, но затем в вызывающей подпрограмме выдается ошибка 450 Неверное количество аргументов или неверное присвоение свойства при вызове функции.   -  person Jerome    schedule 01.07.2018
comment
chillin, ни ?JSON.items(1).items(1), ни ?Json.items(1).items(1) не работают в ближайшем окне. Они выдают ошибку времени выполнения 424 Требуется объект   -  person Jerome    schedule 01.07.2018
comment
chillin ?Json(id) выдает ошибку времени выполнения 5 Недопустимый вызов процедуры или аргумент   -  person Jerome    schedule 01.07.2018
comment
chllin, ?json.items(1).items(1) выдает ошибку 438 Объект не поддерживает это свойство или метод   -  person Jerome    schedule 01.07.2018
comment
Если вы используете 32-битную ОС Windows, есть решение, которое не зависит от какого-либо внешнего преобразователя. Дайте мне знать, если версия совпадает.   -  person SIM    schedule 02.07.2018
comment
@SIM Почему только 32 бит?   -  person omegastripes    schedule 02.07.2018
comment
Ты бесценный актив @omegastripes. Я не знал, что есть способ поиграть с 64-битной версией. Спасибо.   -  person SIM    schedule 02.07.2018
comment
Я использую 64-битную Windows 7. Пожалуйста, не предлагайте изменить JSON, который приходит с сайта Wordpress. Я не контролирую это. Поскольку это решение для одного случая, синтаксический анализ JSON и построение текста JSON для POSt могут быть специфичными для спецификации Wordpress и не должны обрабатывать какие-либо другие случаи.   -  person Jerome    schedule 02.07.2018
comment
Прежде чем комментировать, лучше просмотреть соответствующий контекст @TinMan. Я не большой поклонник JsonConverter. Спасибо.   -  person SIM    schedule 02.07.2018
comment
@SIM достаточно честно. Я думаю, что JSON.Bas от omegastripes просто великолепен. По правде говоря, его пост на самом деле не отвечает на вопрос ОП. Так что, может быть, я был неправ для upvote.   -  person TinMan    schedule 02.07.2018
comment
Ты снова меня неправильно понял, дорогой. Я большой поклонник омегастрипов. Что бы он ни делал, мне это нравится, потому что в этом есть чему поучиться. Что мне не нравится, так это нанимать какой-либо внешний конвертер для работы с vba. Вот и все.   -  person SIM    schedule 02.07.2018
comment
@ omegastripes Теперь мне нужно совершить поездку туда и обратно. У вас есть сопутствующий фрагмент, который реконструирует распакованный JSON и снова упакует его? Это должно быть гораздо более простой задачей, так как содержимое теперь известно.   -  person Jerome    schedule 02.07.2018
comment
@Jerome Если я правильно понимаю, вам нужно преобразовать объект JSON в строку JSON? Если это так, вы можете просто использовать функцию JSON.Serialize().   -  person omegastripes    schedule 02.07.2018
comment
@ omegastripes Общая цель упражнения состоит в том, чтобы запросить сайт Wordpress (как правило, но только Wordpress, никаких других платформ, поэтому структура данных уже четко определена), распаковать JSON в Excel (или Access), затем изменить data (содержимое страницы Wordpress), затем переупаковать JSON и отправить обратно на сайт, тем самым обновив страницу. Теперь, когда ваш код распаковывает его и помещает на лист(ы), мне просто нужно иметь возможность взять тот же лист(ы) и переупаковать данные в JSON так же, как это было до того, как вы его распаковали.   -  person Jerome    schedule 02.07.2018


Ответы (2)


JsonConverter возвращает коллекцию VBA.Collections Сценарии. Словари и значения. Чтобы понять вывод, вам нужно будет проверить TypeName всех возвращаемых значений.

Настоящий вопрос заключается в том, как перемещаться по объекту json (или любому неизвестному объекту в этом отношении) и получать доступ к значениям внутри.

Немедленное окно

Используя объект Immediate Window и json из сообщения OP, я попытаюсь описать мыслительный процесс (в стиле книги, которую обязательно нужно прочитать: Маленький интриган)

' What is json?
?TypeName(JSON)
Collection

'json is a collection
'How big is JSON
?JSON.Count
 1 

'JSON is a collection of 1 Item
'What is Type that Item?
?TypeName(JSON(1))
Dictionary

'JSON(1) is a Dictionary
'What is the first key in the JSON(1) Dictionary?
?JSON(1).Keys()(0)
id

'The first key in the JSON(1) Dictionary is "id"
'What is the Type of the value of "id"?
?TypeName(JSON(1)("id"))
Double

'JSON(1)("id") is a number
'What is its value
?JSON(1)("id")
 1 

Конечно, этот процесс может стать утомительным, учитывая количество вложений в этот объект JSON.

JSON(1)(_links)(кьюри)(1)(шаблон)

Коллекция|Словарь|Словарь|Коллекция|Логическое значение

Поэтому я думаю, что лучше всего написать функцию, которая будет печатать все средства доступа к Immediate Window и идти оттуда.

введите здесь описание изображения

PrintJSONAccessors:Sub

Sub PrintJSONAccessors(JSON As Variant, Optional Prefix As String)
    Dim data As Variant, Key As Variant, Value As Variant
    Dim Accessor As String, ArrayAccessor As String
    Dim n As Long
    If TypeName(JSON) = "Collection" Then
        For n = 1 To JSON.Count
            Accessor = Prefix & "(" & n & ")"
            If TypeName(JSON(n)) = "Dictionary" Or TypeName(JSON(n)) = "Collection" Then
                PrintJSONAccessors JSON(n), Accessor
            Else
                Debug.Print Accessor
            End If
        Next
    Else
        For Each Key In JSON
            If TypeName(Key) = "Dictionary" Or TypeName(Key) = "Collection" Then
                PrintJSONAccessors Key, Prefix
            ElseIf TypeName(JSON(Key)) = "Dictionary" Or TypeName(JSON(Key)) = "Collection" Then
                Accessor = Prefix & "(" & Chr(34) & Key & Chr(34) & ")"
                PrintJSONAccessors JSON(Key), Accessor
            ElseIf TypeName(JSON(Key)) = "Dictionary" Then
                Accessor = Prefix & "(" & Chr(34) & Key & Chr(34) & ")"
                PrintJSONAccessors JSON(Key), Accessor
            ElseIf TypeName(JSON(Key)) = "Variant()" Then
                data = JSON(Key)
                For n = LBound(data) To UBound(data)
                    Accessor = Prefix & "(" & Chr(34) & Key & Chr(34) & ")"
                    ArrayAccessor = Prefix & "(" & Chr(34) & Key & Chr(34) & ")" & "(" & n & ")"
                    If TypeName(data(n)) = "Dictionary" Then
                        PrintJSONAccessors data(n), ArrayAccessor
                    Else
                        Debug.Print ArrayAccessor
                    End If
                Next
            Else
                Accessor = Prefix & "(" & Chr(34) & Key & Chr(34) & ")"
                Debug.Print Accessor
            End If
        Next
    End If
End Sub

Применение:

 PrintJSONAccessors JSON, "?JSON"

Похоже, что MSScriptControl.ScriptControl работает только на 32-битных системах. Я думаю, именно это имел в виду СИМ в своих комментариях. Хотя мой ответ верен ИМО, вы должны игнорировать следующий раздел комментариев.

К сведению: я опубликовал функцию, которая анализирует JSON в массивы и словари функция для возврата JSON-подобных объектов Использование коллекций и массивов VBA в Code Review. Это не замена JsonConverter или JSON.Bas. Это демонстрирует, что вы можете добавить код JScript в CreateObject("MSScriptControl.ScriptControl") и использовать его для обработки JSON.

person TinMan    schedule 02.07.2018
comment
Хороший. Работает ли MSScriptControl.ScriptControl для 64-битной версии? Думал только на 32. Есть ли риски, связанные с использованием ScriptControl? - person QHarr; 02.07.2018
comment
@QHarrI этого не понимал. По какой-то причине Office 365 отказывается устанавливать 64-битную версию моей системы. Спасибо за информацию, Бро! - person TinMan; 02.07.2018
comment
я думаю, вы где-то написали ответ, где вы дали сопоставления для объектов json в jsonconverter.bas, т.е. [] = коллекция и т. д. ..... Я хотел сослаться на ваш ответ, но не могу его найти. Можете ли вы вспомнить этот ответ с какими-либо изменениями, пожалуйста? - person QHarr; 20.08.2019
comment
@QHarr Это тот, Как заставить значения JSON работать в VBA-JSON? [How to get, JSON values to Work in VBA-JSON?](https://stackoverflow.com/a/53494208/9912714) - person TinMan; 20.08.2019
comment
Нет, но тоже полезно - person QHarr; 20.08.2019

Попробуйте код:

    Set json = JsonConverter.ParseJson(s)
    For Each k In json(1)
        Debug.Print k & vbTab & json(1)(k)
    Next

ОБНОВИТЬ

Взгляните на приведенный ниже пример. Импортируйте модуль JSON.bas в проект VBA для обработки JSON.

Option Explicit

Sub Test()

    Dim sJSONString As String
    Dim vJSON
    Dim sState As String
    Dim aData()
    Dim aHeader()
    Dim vResult

    ' Read JSON sample from file C:\Test\sample.json
    sJSONString = ReadTextFile("C:\Test\sample.json", 0)
    ' Parse JSON sample
    JSON.Parse sJSONString, vJSON, sState
    If sState = "Error" Then
        MsgBox "Invalid JSON"
        End
    End If
    ' Get the 1st element from root [] array
    Set vJSON = vJSON(0)
    ' Convert raw JSON to 2d array and output to worksheet #1
    JSON.ToArray vJSON, aData, aHeader
    With Sheets(1)
        .Cells.Delete
        .Cells.WrapText = False
        OutputArray .Cells(1, 1), aHeader
        Output2DArray .Cells(2, 1), aData
        .Columns.AutoFit
    End With
    ' Flatten JSON
    JSON.Flatten vJSON, vResult
    ' Convert flattened JSON to 2d array and output to worksheet #2
    JSON.ToArray vResult, aData, aHeader
    With Sheets(2)
        .Cells.Delete
        .Cells.WrapText = False
        OutputArray .Cells(1, 1), aHeader
        Output2DArray .Cells(2, 1), aData
        .Columns.AutoFit
    End With
    MsgBox "Completed"

End Sub

Sub OutputArray(oDstRng As Range, aCells As Variant)

    With oDstRng
        .Parent.Select
        With .Resize(1, UBound(aCells) - LBound(aCells) + 1)
            .NumberFormat = "@"
            .Value = aCells
        End With
    End With

End Sub

Sub Output2DArray(oDstRng As Range, aCells As Variant)

    With oDstRng
        .Parent.Select
        With .Resize( _
                UBound(aCells, 1) - LBound(aCells, 1) + 1, _
                UBound(aCells, 2) - LBound(aCells, 2) + 1)
            .NumberFormat = "@"
            .Value = aCells
        End With
    End With

End Sub

Function ReadTextFile(sPath As String, lFormat As Long) As String

    ' lFormat -2 - System default, -1 - Unicode, 0 - ASCII
    With CreateObject("Scripting.FileSystemObject").OpenTextFile(sPath, 1, False, lFormat)
        ReadTextFile = ""
        If Not .AtEndOfStream Then ReadTextFile = .ReadAll
        .Close
    End With

End Function

Кстати, аналогичный подход применялся в других ответах.

person omegastripes    schedule 01.07.2018
comment
Я определил это как переменную, и она работает для первых трех элементов, но в элементе 4 она возвращает ошибку 450. Неверное количество аргументов или неверное назначение свойства. - person Jerome; 01.07.2018
comment
@Jerome Нужен образец JSON для дальнейшего понимания и некоторого описания, к каким именно данным вам нужен доступ. - person omegastripes; 01.07.2018
comment
только что опубликовал образец JSON - person Jerome; 01.07.2018
comment
@ Джером, я добавил пример к ответу. - person omegastripes; 01.07.2018
comment
Я импортировал файл .bas, и теперь он находится в собственном модуле, но я не вижу никаких общедоступных функций, поэтому я не знаю, как его использовать. Я также не мог нигде найти никакой документации, никаких заметок по установке. Как мне это использовать? Должен ли я копировать его в свой собственный рабочий модуль? - person Jerome; 02.07.2018
comment
Я скопировал образец, предоставленный вами выше, в свой собственный модуль, но теперь он выдает ошибку времени выполнения 9, индекс которой находится за пределами допустимого диапазона в этой строке: sJSONString = [ & Split(sJSONString, ‹code›[, 2)(1) - person Jerome; 02.07.2018
comment
Если я перепрыгиваю эту ошибку, она выполняется до конца и выдает результат, содержащий ожидаемые данные, но я не могу сказать, что бы она делала, если бы эта строка не потерпела неудачу, поскольку я не понимаю ваш код. - person Jerome; 02.07.2018
comment
Хорошая работа над JSON.Bas. Мне также понравилось, как вы очистили пост. +1∞ - person TinMan; 02.07.2018
comment
@Jerome Предполагалось, что код считывает образец JSON непосредственно из этого сообщения на SO. Кажется, что-то пошло не так на вашем ПК, поэтому я изменил код, и теперь вы должны сохранить образец JSON в локальный файл, см. комментарии в коде. - person omegastripes; 02.07.2018
comment
Все хорошо. Я нашел проблему и немного подправил код. - person Jerome; 02.07.2018