MS Access Parsing Nested Arrays using VBA and JSON - json

Have written code to call API using VBA from MS Access, but having issues extracting nested data. Am able to retrieve data from Items set, but also want to retrieve data from Attributes set where SetName=DG (in the below example there is only 1 AttributeSet, but could be more). Have followed some of the other similar posts, but just not working.
Returned Data:
{"Pagination":
{"NumberOfItems":1,
"PageSize":200,
"PageNumber":1,
"NumberOfPages":1},
"Items":[
{"ProductCode":"TEST",
"ProductDescription":"TEST",
"UnitOfMeasure":
{"Guid":"7e420466-4ced-48df-bb41-1693fe34a32c",
"Name":"EA",
"Obsolete":false},
"NeverDiminishing":false,
"ImageUrl":null,
"SellPriceTier1":
{"Name":"Sell Price Tier 1",
"Value":null},
"SellPriceTier2":
{"Name":"Sell Price Tier 2",
"Value":null},
"Supplier":null,
"AttributeSet":
{"Guid":"c3bd26c9-424a-4786-adbe-7c5a98b8f422",
"SetName":"DG",
"Type":"Product",
"Attributes":[
{"Guid":"6164f12b-2cb9-491c-b932-e6fb050579df",
"Name":"UN",
"Value":"1993",
"IsRequired":false},
{"Guid":"aa13f1dd-2174-4993-b80d-22bf4f4f27da",
"Name":"Technical Name",
"Value":"2K REDUCED",
"IsRequired":false},
{"Guid":"664fbcd6-83be-4afc-b812-22c97ae38949",
"Name":"Flash Point",
"Value":"30",
"IsRequired":false},
{"Guid":"3bc41b7c-bd14-44f6-a6b0-72d1ba84adbb",
"Name":"Pack Group",
"Value":"III",
"IsRequired":false}]},
"IsSellable":true,
"AlternateUnitsOfMeasure":[
{"Guid":"d42f5682-02b3-43fa-a848-46e6023c3b9e",
"Name":"LT",
"ConversionRate":1.0000,
"ForPurchases":true}],
"LastModifiedOn":"\/Date(1674949964652)\/"}
]
}
Code:
Dim key_id, secret_key, URL As String
Dim strJson As String
Dim req As New XMLHTTP60
Dim strModule As String
Dim strFilter As String
Dim rs, rs1 As DAO.Recordset
Dim JsonText As Object
Dim Item As Object
Dim attset As Object
Dim att As Object
key_id = "API ID"
secret_key = "API key"
URL = "https://api.unleashedsoftware.com/"
strModule = "Products?"
strFilter = "productCode=TEST&includeAttributes=True"
strJson = URL + strModule + strFilter
req.Open "GET", strJson, False
req.setRequestHeader "api-auth-id", key_id
req.setRequestHeader "api-auth-signature", Base64HMAC("SHA256", strFilter, secret_key)
req.setRequestHeader "Content-Type", "application/json"
req.setRequestHeader "Accept", "application/json"
req.sEnd
Set JsonText = JsonConverter.ParseJson(req.responseText)
For Each Item In JsonText("Items")
Set rs = CurrentDb.OpenRecordset("aProduct")
With rs
.AddNew
!prodcode = Item("ProductCode")
!proddesc = Item("ProdutDescription")
.Update
End With
For Each att In Item("Attributes")
'get Attribute Value
Set rs1 = CurrentDb.OpenRecordset("aProdDGAttribute")
With rs1
.AddNew
!Name = att("Name")
!Value= att("Value")
.Update
End With
Next att
Next Item
MsgBox "done"
locals window screenshot:

Consider this which works for the given sample showing only one AttributeSet:
Dim First As Variant, Second As Variant
Dim items As Object, atts As Object
Set items = jSon("Items")
For Each First In items
Debug.Print First("ProductCode")
Debug.Print First("ProductDescription")
Debug.Print First("AttributeSet")("SetName")
Set atts = First("AttributeSet")("Attributes")
For Each Second In atts
Debug.Print Second("Name")
Debug.Print Second("Value")
Next
Next
Guidance from Parse JSON objects and collection using VBA

Related

Parsing JSON into Excel but having an Error

I have been using below code to convert the data from JSON to Excel but below JSON format is not converting into excel and having an error Run time error: Invalid procedure call or argument on the line ws.Cells(r, "C").Value = JSON("sku")
Here is my code that i have been using. I do not know why the error is appearing when it works for other JSON format instead of this one.
Your help will be appreciated.
Dim objRequest As Object
Dim strUrl As String
Dim blnAsync As Boolean
Dim strResponse As String
Dim idno As Long
Dim ws As Worksheet
Dim JSON As Object
Dim lrow As Long
Set ws = Sheet4
lrow = ws.Range("C" & ws.Rows.Count).End(xlUp).Row
Set objRequest = CreateObject("MSXML2.XMLHTTP")
strUrl = "url"
blnAsync = True
With objRequest
.Open "GET", strUrl, blnAsync
.setRequestHeader "Content-Type", "application/json"
.send
While objRequest.readyState <> 4
DoEvents
Wend
strResponse = .ResponseText
End With
Set JSON = ParseJson(strResponse)
' Debug.Print strResponse
r = 2
ws.Cells(r, "C").Value = JSON("sku")
'r = r + 1
I'm not sure if there is a option for this in JSONConverter but as far as I know, it doesn't like to parse JSON string that starts with a collection so I would usually create a key manually so that it will convert properly.
Below example also shows how you can loop through the collection and get the value for sku:
'.... Continue after you post the API...
Dim strResponse As String
strResponse = .responseText
Dim resultDict As Object
Set resultDict = ParseJson("{""result"":" & strResponse & "}")
Dim i As Long
Dim resultNum As Long
resultNum = resultDict("result").Count
For i = 1 To resultNum
Debug.Print resultDict("result")(i)("id")
Debug.Print resultDict("result")(i)("sku")
Debug.Print resultDict("result")(i)("upc")
'Loop through skuList collection
Dim j As Long
For j = 1 To resultDict("result")(i)("skuList").Count
Debug.Print vbTab & resultDict("result")(i)("skuList")(j)("id")
Debug.Print vbTab & resultDict("result")(i)("skuList")(j)("sku")
Debug.Print vbTab & resultDict("result")(i)("skuList")(j)("skuTitle")
Next j
Next i

Webscraping in VBA where some HTML information has no way to refer to it

I have this VBA script scraping from this URL https://accessgudid.nlm.nih.gov/devices/10806378034350
I want the LOT,SERIAL, and EXPIRATION information which in the below pic, has a "Yes" or "No" inside the HTML.
How do I return just that Yes or No information?
Sub GetInnerInformation(HTMLPage As MSHTML.HTMLDocument)
Dim HTMLResult As MSHTML.IHTMLElement
Dim HTMLResults As MSHTML.IHTMLElementCollection
Dim HTMLRow As MSHTML.IHTMLElement
Dim HTMLCell As MSHTML.IHTMLElement
Dim RowNum As Long, ColNum As Integer
Set HTMLResults = HTMLPage.getElementsByClassName("device-attribute")
For Each HTMLResult In HTMLResults
If (HTMLResult.innerText Like "*Lot*") = True Then
Debug.Print HTMLResult.innerText, HTMLResult.outerText, HTMLResult.innerHTML
End If
Next HTMLResult
End Sub
In my Immediate Window I get:
Lot or Batch Number: Lot or Batch Number: Lot or Batch Number:
So no reference to the Yes or No that is in the HTML.
HTML Parser:
You could use a css attribute = value selector to target the span with [?] that is just before the div of interest. Then climb up to shared parent with parentElement, and move to the div of interest with NextSibling. You can then use getElementsByTagName to grab the labels nodes, and loop that nodeList to write out required info. To get the values associated with labels, you again need to use NextSibling to handle the br children within the parent div.
I use xmlhttp to make the request which is faster than opening a browser.
Option Explicit
Public Sub WriteOutYesNos()
Dim html As MSHTML.HTMLDocument
Set html = New MSHTML.HTMLDocument
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://accessgudid.nlm.nih.gov/devices/10806378034350", False
.send
html.body.innerHTML = .responseText
End With
Dim nodes As Object, i As Long
Set nodes = html.querySelector("[title*='A production identifier (PI) is a variable']").parentElement.NextSibling.getElementsByTagName("LABEL")
For i = 0 To nodes.Length - 3
With ActiveSheet
.Cells(i + 1, 1) = nodes(i).innerText
.Cells(i + 1, 2) = nodes(i).NextSibling.NodeValue
End With
Next
End Sub
JSON Parser:
Data is also available as json which means you can use a json parser to handle. I use jsonconverter.bas as the json parser to handle response. Download raw code from here and add to standard module called JsonConverter . You then need to go VBE > Tools > References > Add reference to Microsoft Scripting Runtime. Remove the top Attribute line from the copied code.
Option Explicit
Public Sub WriteOutYesNos()
Dim json As Object, ws As Worksheet, results(), i As Long, s As String
Set ws = ThisWorkbook.Worksheets("Sheet1")
results = Array("lotBatch", "serialNumber", "manufacturingDate")
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://accessgudid.nlm.nih.gov/devices/10806378034350.json", False
.send
Set json = JsonConverter.ParseJson(.responseText)
End With
With ws
For i = LBound(results) To UBound(results)
.Cells(i + 1, 1) = results(i)
.Cells(i + 1, 2).Value = IIf(json(results(i)), "Yes", "No")
Next
End With
End Sub
XML Parser:
Results also come as xml which you can parse with xml parser provided you handle the default namespace appropriately:
Option Explicit
Public Sub WriteOutYesNos()
Dim xmlDoc As Object, ws As Worksheet, results(), i As Long
Set xmlDoc = CreateObject("MSXML2.DOMDocument")
Set ws = ThisWorkbook.Worksheets("Sheet1")
results = Array("lotBatch", "serialNumber", "manufacturingDate")
With xmlDoc
.validateOnParse = True
.setProperty "SelectionLanguage", "XPath"
.setProperty "SelectionNamespaces", "xmlns:i='http://www.fda.gov/cdrh/gudid'"
.async = False
If Not .Load("https://accessgudid.nlm.nih.gov/devices/10806378034350.xml") Then
Err.Raise .parseError.ErrorCode, , .parseError.reason
Exit Sub
End If
End With
With ws
For i = LBound(results) To UBound(results)
.Cells(i + 1, 1) = results(i)
.Cells(i + 1, 2).Value = IIf(xmlDoc.SelectSingleNode("//i:" & results(i)).Text, "Yes", "No")
Next
End With
End Sub
Tinkered around and found it. I had to hardcode the results a little but here is what I got. Let me know if you've found a more elegant answer!
Sub GetInnerInformation(HTMLPage As MSHTML.HTMLDocument)
Dim HTMLResult As MSHTML.IHTMLElement
Dim HTMLResults As MSHTML.IHTMLElementCollection
Dim HTMLRow As MSHTML.IHTMLElement
Dim HTMLCell As MSHTML.IHTMLElement
Dim RowNum As Long, ColNum As Integer
Dim Lot As Boolean
Dim Serial As Boolean
Dim Expiration As Boolean
Set HTMLResults = HTMLPage.getElementsByClassName("expandable-device-content")
For Each HTMLResult In HTMLResults
If (HTMLResult.innerText Like "*Lot or Batch Number*") = True Then
Debug.Print HTMLResult.innerText
If HTMLResult.innerText Like "*Lot or Batch Number: Yes*" Then
Lot = True
End If
If HTMLResult.innerText Like "*Lot or Batch Number: No*" Then
Lot = False
End If
If HTMLResult.innerText Like "*Serial Number: Yes*" Then
Serial = True
End If
If HTMLResult.innerText Like "*Serial Number: No*" Then
Serial = False
End If
If HTMLResult.innerText Like "*Expiration Date: Yes*" Then
Serial = True
End If
If HTMLResult.innerText Like "*Expiration Date: No*" Then
Serial = False
End If
Debug.Print Lot, Serial, Expiration
End If
Next HTMLResult
End Sub

Why can't I assign to a variable a JSON value that I can see in Debug mode?

I’ve run into an issue retrieving values from a non-itemized JSON object. I thought it was simple do so … Just reference the object with the field you want (e.g. JSON(“title”). But I cannot retrieve a value even though it IS there.
This code demonstrates what I’m talking about. (Be sure to put a breakpoint on the “next” line, or it will run for a while.) The strID and strTitle values are not assigned and do not print out. But if you go to the immediate window and type
? JSON2("ID")
? JOON2(“title”)
You get the values. What am I doing wrong? Why can’t I get these values into variables?
Sub testMovie2()
Dim Url As String, data As String, data2 As String
Dim xml As Object, JSON As Object, JSON2 As Object, colObj As Object, colobj2 As Object, item, item2
Dim strID As String, strTitle As String
Url = "https://www.tiff.net/data/films-events-2018.json"
data = getHTTP(Url)
Set JSON = JsonConverter.ParseJson(data)
Set colObj = JSON("items")
For Each item In colObj
Url = "https://www.tiff.net/data/films/" & item("id") & ".JSON"
data2 = getHTTP(Url)
Set JSON2 = JsonConverter.ParseJson(data2)
strID = JSON2("ID")
Debug.Print strID
strTitle = JSON2("Title")
Debug.Print strTitle
Next
End Sub
JSON2 is a dictonary object and to retrieve element from dictonary use below
with key
JSON2.item("id")
JSON2.item("title")
OR
with index
JSON2.Items()(4)
JSON2.Items()(5)
By default dictionary objects are case sensitive
So JSON2("ID") not equals to JSON2("id")
To make it case insensitive use:
JSON2.CompareMode = vbTextCompare
Code:
Sub testMovie2()
Dim url As String, data As String, data2 As String
Dim xml As Object, JSON As Object, JSON2 As Object, colObj As Object, colobj2 As Object, item, item2
Dim strID As String, strTitle As String
url = "https://www.tiff.net/data/films-events-2018.json"
data = getHTTP(url)
Set JSON = JsonConverter.ParseJson(data)
Set colObj = JSON("items")
For Each item In colObj
url = "https://www.tiff.net/data/films/" & item("id") & ".JSON"
data2 = getHTTP(url)
Set JSON2 = JsonConverter.ParseJson(data2)
strID = JSON2.item("id")
Debug.Print strID
strTitle = JSON2.item("title")
Debug.Print strTitle
Next
End Sub
Function getHTTP(url) As String
Dim data As String
Dim xml As Object
Set xml = CreateObject("MSXML2.ServerXMLHTTP")
With xml
.Open "GET", url, False
.setRequestHeader "Content-Type", "text/json"
.send
data = .responseText
End With
getHTTP = data
End Function

VBA Parsing with Json

I am trying to parse this with JsonConverter.bas but everything i have tried is not working, I am coming to the conclusion that this is not json but i cannot figure else what it is and well how to parse it!
"{
""success"": true,
""delivered"": true,
""contactDetailsRequired"": false,
""message"": ""Signed For by: 29 CARDIFF EARLY MAI"",
""signature"": ""https://webservices.thedx.co.uk/PodImage/ImageHandler.ashx?tn=505012368126"",
""date"": ""08-02-2018"",
""serviceLevelName"": ""Tracked Mail"",
""time"": ""07:30:00"",
""trackedProductName"": ""TMS""
}"
I am trying to get each value as a string or an array which then i will insert on sheet, here is the code I have been playing with,
Dim httpObject As Object
Set httpObject = CreateObject("MSXML2.XMLHTTP")
sURL = "https://www.dxdelivery.com/umbraco/Api/TrackingApi/TrackingData?trackingNumber=505012368126&postcode=&trackingType=0"
sRequest = sURL
httpObject.Open "GET", sRequest, False
httpObject.send
sgetresult = httpObject.responseText
MsgBox (sgetresult)
Sheets("sheet1").Range("A1") = sgetresult
Dim FSO As New FileSystemObject
Dim JsonTS As TextStream
Dim JsonText As String
Dim Parsed As Dictionary
' Read .json file
' Parse json to Dictionary
' "values" is parsed as Collection
' each item in "values" is parsed as Dictionary
Set Parsed = JsonConverter.ParseJson(sgetresult)
MsgBox Parsed("""success""")
Hope you can help,
Thank you.
If the JsonConverter.bas (https://github.com/VBA-tools/VBA-JSON) is properly installed, then the following works for me:
Sub test()
Dim httpObject As Object
Set httpObject = CreateObject("MSXML2.XMLHTTP")
sURL = "https://www.dxdelivery.com/umbraco/Api/TrackingApi/TrackingData?trackingNumber=505012368126&postcode=&trackingType=0"
sRequest = sURL
httpObject.Open "GET", sRequest, False
httpObject.send
sGetResult = httpObject.responseText
Set oJSON = JsonConverter.ParseJson(sGetResult)
MsgBox oJSON("success")
For Each sItem In oJSON
MsgBox sItem & " = " & oJSON(sItem)
Next
End Sub

VBA access to a json property without name property

I'm trying to access in VBA to over 508 "tank_id"s from a JSON file as you can see here.
I'm using cStringBuilder, cJSONScript and JSONConverter to parse the JSON file.
My main issue is that I can't pass threw all those ids because I don't know how to get the "1" "33" "49" "81" that are without names.
Here si the code I tried to get them, without success.
Const myurl2 As String = "https://api.worldoftanks.eu/wot/encyclopedia/vehicles/?application_id=demo&fields=tank_id"
Sub List_id_vehicules()
Dim strRequest
Dim xmlHttp: Set xmlHttp = CreateObject("msxml2.xmlhttp")
Dim response As Object
Dim rows As Integer
Dim counter As Integer
Dim j As String
Dim k As Integer: k = 2
Dim url As String
url = myurl2
xmlHttp.Open "GET", url, False
xmlHttp.setRequestHeader "Content-Type", "text/xml"
xmlHttp.send
While Not xmlHttp.Status = 200 '<---------- wait
Wend
Set response = ParseJson(xmlHttp.ResponseText)
rows = response("meta")("count")
For counter = 1 To rows
j = counter
Dim yop As String
yop = "data[" & j & "][" & j & "]"
Sheets(2).Cells(1 + counter, 1).Value = response('data[counter]')['tank_id']
Next counter
END Sub
Could someone help me ?
The JSONConverter essentially parses the JSON text string into a set of nested Dictionary objects. So when the ParseJson function returns an Object, it's really a Dictionary. Then, when you access response("meta"), the "meta" part is the Key to the Dictionary object. It's the same thing as you nest down through the JSON.
So when you try to access response("data")("3137"), you're accessing the Dictionary returned by response("data") with the key="3137". Now the trick becomes how to get all the Keys from the response("data") object.
Here's a sample bit of code to illustrate how you can list all the tank IDs in the JSON data section:
Option Explicit
Sub ListVehicleIDs()
Const jsonFilename As String = "C:\Temp\tanks.json"
Dim fileHandle As Integer
Dim jsonString As String
fileHandle = FreeFile
Open jsonFilename For Input As #fileHandle
jsonString = Input$(LOF(fileHandle), #fileHandle)
Close #fileHandle
Dim jsonObj As Object
Set jsonObj = ParseJson(jsonString)
Dim tankCount As Long
tankCount = jsonObj("meta")("count")
Dim tankIDs As Dictionary
Set tankIDs = jsonObj("data")
Dim tankID As Variant
For Each tankID In tankIDs.keys
Debug.Print "Tank ID = " & tankID
Next tankID
End Sub