Traversing Nested JSON using VBA - json

I have a JSON I am trying to parse in VBA. The JSON looks similar to the following:
{
"participantEligibilityResults": [
{
"eligibilityResult": {
"participantId": "HSA92a",
"clientId": "NIRCCCONFIG",
"environment": "CONFIG",
"errorReason": null,
"previousEvent": {
"eventDate": "2019-01-01",
"eventReason": "7",
"eligibilityDetails": [
{
"standardBenefitAreaId": "SPLIFE",
"benefitOptionId": "1XPay",
"coverageLevelId": "PPSP",
"employeeMonthlyCost": 216.67,
"employerMonthlyCost": 0.0,
"benefitProgramId": "ProgH"
},
{
"standardBenefitAreaId": "SPLIFE",
"benefitOptionId": "NoCoveragePay",
"coverageLevelId": null,
"employeeMonthlyCost": 0.0,
"employerMonthlyCost": 0.0,
"benefitProgramId": "ProgH"
}
],
"dependents": []
},
"currentEvent": {
"eventDate": "2020-03-14",
"eventReason": "5",
"eligibilityDetails": [
{
"standardBenefitAreaId": "BASICCHLIFE",
"benefitOptionId": "BCHWaive",
"coverageLevelId": null,
"employeeMonthlyCost": 0.0,
"employerMonthlyCost": 0.0,
"benefitProgramId": "ProgH",
"beneficiaryCollection": "Not Applicable",
"maxCoverageAmount": 0.0,
"minCoverageAmount": 0.0,
"coverageAmount": 0.0,
"preTax": true,
"postTax": false,
"userDefinedTaxability": false,
"numberOfPayPeriods": 52,
"payperiodsRemaining": 42.0
},
{
"standardBenefitAreaId": "DENTAL",
"benefitOptionId": "DentalPPO",
"coverageLevelId": "PPFAM2",
"employeeMonthlyCost": 29.17,
"employerMonthlyCost": 125.0,
"benefitProgramId": "ProgH",
"beneficiaryCollection": "Not Applicable",
"maxCoverageAmount": 0.0,
"minCoverageAmount": 0.0,
"preTax": true,
"postTax": false,
"userDefinedTaxability": false,
"numberOfPayPeriods": 52,
"payperiodsRemaining": 42.0
}
],
"dependents": [
{
"fullName": "Allison Drew ",
"dependentId": "5d82c4bf-609d-4c2f-8c1b-7d8fdd8b9fde",
"relationshipType": "Spouse",
"birthDate": "1980-01-01",
"activeIndicator": true,
"approvedIndicator": true,
"studentIndicator": false,
"coverages": [
{
"standardBenefitAreaId": "DENTAL",
"benefitOptionId": "NoCoverageDental",
"dependentCoverageRequired": false,
"activeCourtOrdered": false
},
{
"standardBenefitAreaId": "MEDICAL",
"benefitOptionId": "NoCoverageMedical",
"dependentCoverageRequired": false,
"activeCourtOrdered": false
}
]
},
{
"fullName": "Adam Drew ",
"dependentId": "d3f97b64-4a50-4dea-bec8-51d3db39352a",
"relationshipType": "Child",
"birthDate": "2012-01-01",
"activeIndicator": true,
"approvedIndicator": true,
"studentIndicator": false,
"coverages": [
{
"standardBenefitAreaId": "DENTAL",
"benefitOptionId": "NoCoverageDental",
"dependentCoverageRequired": false,
"activeCourtOrdered": false
},
{
"standardBenefitAreaId": "MEDICAL",
"benefitOptionId": "NoCoverageMedical",
"dependentCoverageRequired": false,
"activeCourtOrdered": false
}
]
}
]
}
},
"changes": []
}
]
}
I am currently utilizing VBA-JSON from https://github.com/VBA-tools/VBA-JSON to parse the JSON.
JsonOptions.AllowUnquotedKeys = True
Set JSON = JsonConverter.ParseJson(jsonResponse)
Ultimately, I am looking to access participantResults | eligibilityResult | currentEvent | eligibilityDetails and participantResults | eligibilityResult | currentEvent | dependents. I have tried beginning to traverse the JSON using something like:
For Each Eligibility In JSON("participantEligibilityResults")
For Each Detail In Eligibility("eligibilityResult")
'DO SOMETHING HERE
Next
Next
Unfortunately, once I parse at the participantEligibilityResults level, I am unable to access the levels below. I get an error "Object doesn't support this property or method." Can someone point me in the right direction?

Everything enclosed in {} will be output as a dictionary, everything enclosed in [] will be a collection. You just need to follow the nesting to get where you want.
Sub Test()
Dim result As String
Dim Item, a
Dim parsedResult As Object, obj, node, k
'loading from a cell for testing...
Set parsedResult = JsonConverter.ParseJson(Sheet2.Range("A1").Value)
Set obj = parsedResult("participantEligibilityResults")(1)("eligibilityResult")
Set node = obj("currentEvent")("eligibilityDetails")(1)
DumpJSon node 'see below
Set node = obj("currentEvent")("dependents")(1)
DumpJSon node 'see below
End Sub
If there are specific items you want, then trying to create nested loops to get to them will likely not be very useful - identify the paths you want and access the values directly. If you need to (eg) loop over a collection then that needs to be part of your approach.
It's sometimes useful to double-check what you have in your parsed result, so you can use this to dump it to the Immediate window (the whole thing or only parts of it)
Sub DumpJSon(obj, Optional level As Long = 0)
Const LEVEL_STEP As Long = 5
Dim k, v, n, s, tmp
If TypeName(obj) = "Dictionary" Then
For Each k In obj.keys
s = String(level, "-") & k & " = "
If IsObject(obj(k)) Then
Debug.Print s & IIf(obj(k).Count = 0, "Empty ", "") & _
TypeName(obj(k))
DumpJSon obj(k), level + LEVEL_STEP
Else
Debug.Print s & obj(k)
End If
Next k
ElseIf TypeName(obj) = "Collection" Then
n = 1
For Each v In obj
s = String(level, "-") & "(Item #" & n & ") "
If IsObject(v) Then
Debug.Print s & IIf(v.Count = 0, "Empty ", "") & _
TypeName(v)
DumpJSon v, level + LEVEL_STEP
Else
Debug.Print s & v
End If
n = n + 1
Next v
End If
End Sub

Related

How to populate excel with VBA from nested JSON?

JSON response
"error": null,
"metadata": {
"total": 1,
"limit": 1000,
"offset": 0
},
"data": [
{
"id": 1,
"description": "10 licenses",
"closeDate": "2018-05-22",
"date": "2018-05-22",
"notes": "",
"user": {
"id": 1,
"name": "Gustav Petterson",
"role": null,
"email": "apidocs#upsales.com"
},
"client": {
"name": "Pied piper",
"id": 2,
"users": [
{
"id": 1,
"name": "Gustav Petterson",
"role": null,
"email": "apidocs#upsales.com"
}
]
},
"contact": null,
"project": null,
"regDate": "2018-05-22T11:08:26.000Z",
"stage": {
"name": "Won - Order",
"id": 12
},
"probability": 100,
"modDate": "2018-05-22T11:13:59.000Z",
"clientConnection": null,
"currencyRate": 1,
"currency": "SEK",
"locked": 0,
"custom": [
{
"value": "2018-05-23",
"valueDate": "2018-05-23",
"orgNumber": 20180523,
"fieldId": 1
}
],
"orderRow": [
{
"id": 1,
"quantity": 1,
"price": 10000,
"discount": 0,
"custom": [],
"productId": 1,
"sortId": 1,
"listPrice": 10000,
"product": {
"name": "Example product",
"id": 1,
"category": null
}
}
],
"value": 10000,
"weightedValue": 10000,
"valueInMasterCurrency": 10000,
"weightedValueInMasterCurrency": 10000,
"agreement": null,
"userRemovable": true,
"userEditable": true
}
]
}
So, I've tried to parse this into my sheets but struggled for quite some time now. What I wanted to do is get all order details into a sheet, but several levels of nested parts were constantly bugging me.
In "Data" everything goes well until it runs till the first nested item "users" that is Dictionary, or "Clients" that is Collection. I tried to run the next loop to fetch nested items but I created an even bigger mess.
Sub GetOrders()
Dim sGetResult As String
Dim d_lr As Double
Dim httpObject As Object
Dim dict_json As Object
Dim objData
Dim objOrder
d_lr = LastRow(ActiveSheet)
Set httpObject = CreateObject("MSXML2.XMLHTTP")
sURL = "https://integration.upsales.com/api/v2/orders?token=" & wAdmin.Range("C4") & "&probability=100"
sRequest = sURL
httpObject.Open "GET", sRequest, False
httpObject.setRequestHeader "Accept: ", "application/json"
httpObject.Send
sGetResult = httpObject.responseText
Set dict_json = JsonConverter.ParseJson(sGetResult)
Set objData = dict_json("data")
For Each objOrder In objData
For i = 0 To objOrder.Count - 1
Debug.Print objOrder.Items()(i)
Next I
Next objOrder
End Sub
You need to test the object type and recurse accordingly.
Set dict_json = JsonConverter.ParseJson(sGetResult)
Set objdata = dict_json("data")(1)
Dim k, v, u, p
For Each k In objdata
If VarType(objdata(k)) = 9 Then ' object
If k = "user" Then
For Each u In objdata(k)
Debug.Print "user", u, objdata(k)(u)
Next
End If
If k = "client" Then
For Each u In objdata(k)
If u = "users" Then
' for each loop for users
For i = 1 To objdata(k)(u).Count
For Each p In objdata(k)(u)(i)
Debug.Print "users", i, p, objdata(k)(u)(i)(p)
Next
Next
Else
Debug.Print "client", u, objdata(k)(u)
End If
Next
End If
Else
Debug.Print k, objdata(k)
End If
Next

Access nested values in JSON-Object in VBA

I would like to get data from a JSON-Object, that I got from a Rest-API, with VBA to display some data into an Excel-Worksheet. I'm using the library (VBA-JSON v2.3.1 JsonConverter).
I have the following JSON-Object:
{
"devices": [
{
"data": [
{
"id": 0,
"name": "Hello"
},
{
"id": 1,
"name": "How are you?"
},
{
"id": 2,
"name": "Bye"
}
],
"type": "LORA"
}
],
"includedTypes": [
"LORA"
]
}
I want to get the objects in the array from "data".
My VBA-Code is this:
Dim js1Object As Object
Dim response1 As String
strUrl = "https://XXXXXXXXXXXdevices
Set hReq = CreateObject("MSXML2.XMLHTTP")
With hReq
.Open "GET", strUrl, False
.SetRequestHeader "Authorization", "Bearer " & apitoken
.Send
response1 = hReq.responseText
MsgBox response1
Set js1Object = JsonConverter.ParseJson(response1)
j = 31
For Each item In js1Object("devices")
ws.Cells(j, 7) = item("id")
ws.Cells(j, 10) = item("name")
j = j + 1
Next
MsgBox (response1)
End With
How can I access the values from "data"?
If the JSON would look like the object below, my code would work. But my problem is, that the response that I get, is more nested and I can't directly access "data".
{
"devices": [
{
"id": 0,
"name": "Hello"
},
{
"id": 1,
"name": "How are you?"
},
{
"id": 2,
"name": "Bye"
}
]
}
I just don't know, how to access deeper values in JSON-Object. The solutions from similar questions with print are not working with my code.
Thanks for helping me!
Your "root" json object is a Dictionary - the key "devices" is a Collection object, and the first element is another dictionary with two keys "data" and "type".
"data" is another Collection of Dictionaries, so you can do this to get to the contained id and name values:
Dim Json As Object, data, d
'reading json from a worksheet cell...
Set Json = JsonConverter.ParseJson(Range("A5").Value)
Set data = Json("devices")(1)("data") 'Dictionary key->Collection index->Dictionary key
For Each d In data
Debug.Print d("id"), d("name")
Next d
Output:
0 Hello
1 How are you?
2 Bye

Convertion from array to json array isnt made properly using vb.net?

I am trying to convert an array into a json array. What I have is:
I have created a class where I declare the fields that I am going to use.
Public Class Response
Public container As Array
Public signature As Tuple(Of Object, String, Integer, String, Object, String)
Sub New()
Me.container = Nothing
Me.signature = Nothing
End Sub
Sub New(ByVal container As Array,
ByVal signature As Tuple(Of Object, String, Integer, String, Object, String))
Me.container = container
Me.signature = signature
End Sub
End Class
And the function where I want to convert them in JSON in order to use:
Public Function GetResponse()
Dim response As New Response
response.container = {"none", False}
response.signature = New Tuple(Of Object, String, Integer, String, Object, String)({10, 10},
"IT", 1, "Testing", {100, 100}, "Test Signature")
Dim JSONString As String = JsonConvert.SerializeObject(response)
Return JSONString
End Function
What I want it to look like is:
{ "container": {
"type": "none",
"single": false
},
"signature": {
"coordinates": {
"x": 10,
"y": 10
},
"location": "IT",
"page": 1,
"reason": "Testing",
"size": {
"height": 100,
"width": 100
},
"value": "Test Signature"
}
}
But what it looks like is:
{
"container": [
"none", false
],
"signature": {
"Item1": [10, 10],
"Item2": "IT",
"Item3": 1,
"Item4": "Testing",
"Item5": [100, 100],
"Item6": "Test Signature"
}
}
I am new to this, I would appriciate any help :) Thanks in advance!
That's the problem with using a Tuple; the properties don't have names you can choose. If you don't want to create a full on class for this data (and take a google for "Paste JSON as Classes"; there isn't much excuse for not having them), use an anonymous type:
Dim response = New With { _
.container = New With { .type = "none", .single = false }, _
.signature = New With { _
.coordinates = New With { .x = 10, .y = 10 }, _
.location = "IT", _
.someOtherName = someOtherValue, _
... etc ...
}

VBA: count items in JSON response string / trello get Cards count in a List

I'm using VBA-web (https://vba-tools.github.io/VBA-Web/) to access trello api, to get cards in a list
My function looks like that:
Public Function CountCardsinList(ListId As String) As Integer
WebHelpers.EnableLogging = False
Dim TrelloClient As New WebClient
TrelloClient.BaseUrl = "https://api.trello.com/1/"
Dim Request As New WebRequest
Request.Format = WebFormat.Json
Request.ResponseFormat = Json
Request.Resource = "lists/{ListId}/cards"
Request.AddUrlSegment "ListId", ListId
Request.AddQuerystringParam "key", TrelloAPIKey
Request.AddQuerystringParam "token", TrelloAPIToken
Request.AddQuerystringParam "filter", "open"
Dim Response As WebResponse
Set Response = TrelloClient.Execute(Request)
If Response.StatusCode = WebStatusCode.Ok Then
Debug.Print Response.Content '
'Response.Data("idList").Count
Debug.Print "CountCardsinList =>>> " & Response.Content
CountCardsinList = Response.Data("idList").Count
Else
MsgBox Response.StatusDescription, vbCritical, "Error " & Response.StatusCode
CountCardsinList = ""
End If
Debug.Print "CountCardsinList =>>> " & Response.Content
'Set CountCardsinList = Request
End Function
I receive correct JSON reply from the api:
[{
"id": "584e798dd570ae187b293e5b",
"checkItemStates": null,
"closed": false,
"dateLastActivity": "2016-12-30T09:24:57.531Z",
"desc": "",
"descData": null,
"idBoard": "57873ba94794058756fa0a96",
"idList": "57873bb3a725f734089702b2",
"idMembersVoted": [],
"idShort": 90,
"idAttachmentCover": null,
"manualCoverAttachment": false,
"idLabels": ["57873ba984e677fd3683bef8"],
"name": "card name / other stuff",
"pos": 1999.9923706054688,
"shortLink": "izoqvWJk",
"badges": {
"votes": 0,
"viewingMemberVoted": false,
"subscribed": false,
"fogbugz": "",
"checkItems": 0,
"checkItemsChecked": 0,
"comments": 0,
"attachments": 0,
"description": false,
"due": "2016-12-26T11:00:00.000Z",
"dueComplete": false
},
"dueComplete": false,
"due": "2016-12-26T11:00:00.000Z",
"idChecklists": [],
"idMembers": ["54f0cc079bf18f2798dda8bd"],
"labels": [{
"id": "57873ba984e677fd3683bef8",
"idBoard": "57873ba94794058756fa0a96",
"name": "Urgent",
"color": "red",
"uses": 14
}],
"shortUrl": "https://trello.com/c/vfvfdvdfv",
"subscribed": false,
"url": "https://trello.com/c/fdvfdvdfv/cfvdfv"
},
{
"id": "5832c2fa7f55fe5637d972ea",
"checkItemStates": null,
"closed": false,
"dateLastActivity": "2016-12-30T09:25:09.222Z",
"desc": "",
"descData": null,
"idBoard": "57873ba94794058756fa0a96",
"idList": "57873bb3a725f734089702b2",
"idMembersVoted": [],
"idShort": 80,
"idAttachmentCover": null,
"manualCoverAttachment": false,
"idLabels": ["57873ba984e677fd3683bef6"],
"name": "other card name",
"pos": 2023.9922790527344,
"shortLink": "XhUPgcsD",
"badges": {
"votes": 0,
"viewingMemberVoted": false,
"subscribed": false,
"fogbugz": "",
"checkItems": 0,
"checkItemsChecked": 0,
"comments": 0,
"attachments": 0,
"description": false,
"due": "2016-12-30T15:00:00.000Z",
"dueComplete": false
},
"dueComplete": false,
"due": "2016-12-30T15:00:00.000Z",
"idChecklists": [],
"idMembers": ["54fdbe1a8ecdf184596c7c07"],
"labels": [{
"id": "57873ba984e677fd3683bef6",
"idBoard": "57873ba94794058756fa0a96",
"name": "Medium",
"color": "yellow",
"uses": 1
}],
"shortUrl": "https://trello.com/c/XhdfvdfvUPgcsD",
"subscribed": false,
"url": "https://trello.com/c/XhUPgcsfdvdffvD/
"
But I cannot correctly count idList -> and I'm trying to get number of cards in a list, by using Response.Data("idList").Count
Any information how to do it proper way? or which is the best way to parse JSON data?
General:
Your JSON isn't properly closed. I added }] to the end to close and placed in cell A1 of activesheet (as I don't have the API info). I then read that in from the cell as if it were response text.
Process:
I then used JSONConverter to parse this string from the sheet. This requires you to also add a reference to Microsoft Scripting Runtime via VBE > Tools > References.
The returned object is a collection of dictionaries. I test each dictionary for the existence of an idList key and if present add 1 to the variable itemCount, which keeps track of how many idLists there are.
Code:
Public Sub GetInfoFromSheet()
Dim jsonStr As String, item As Object, json As Object, itemCount As Long
jsonStr = [A1]
Set json = JsonConverter.ParseJson(jsonStr)
For Each item In json 'collection
If item.Exists("idList") Then itemCount = itemCount + 1
Next item
Debug.Print "idList count: " & itemCount
End Sub

Iterating through nested objects with VBJSON

I am attempting to connect to the SmartSheet API through VBA to pull the contents into an Excel sheet. I found the VBJSON library which has helped me a bit but I am struggling with iterating through the objects and pulling specific values.
I want to access the contents of the "Value" attribute for each row then do the same for subsequent rows. My biggest problem is that I do not know how this VBJSON library works since I cannot find any documentation on it and there are only a few examples and they deal with relatively straightforward JSON examples.
Desired Output
Row 1 Column 1 Content | Row 1 Column 2 Content
Row 2 Column 1 Content | Row 2 Column 2 Content
JSON
{
"id": 1,
"name": "Sheet Name",
"columns": [
{
"id": 1,
"index": 0,
"title": "Title of Column",
"type": "TEXT_NUMBER",
"primary": true
},
{
"id": 2,
"index": 1,
"title": "Title of Second Column",
"type": "TEXT_NUMBER"
},
],
"rows": [
{
"id": 1,
"rowNumber": 1,
"cells": [
{
"type": "TEXT_NUMBER",
"value": "Row 1 Column 1 Content",
"columnId": 1,
},
{
"type": "TEXT_NUMBER",
"value": "Row 1 Column 2 Content",
"columnId": 2,
},
],
"locked": true,
"lockedForUser": true,
"expanded": true,
"createdAt": "2013-10-11T13:43:24-05:00",
"modifiedAt": "2013-11-12T15:13:54-06:00"
},
{
"id": 2276445193037700,
"rowNumber": 2,
"cells": [
{
"type": "TEXT_NUMBER",
"value": "row 2 column 1 content",
"columnId": 1,
},
{
"type": "TEXT_NUMBER",
"value": "row 2 column 2 content",
"columnId": 2,
}
]
}
VBJSON library
http://www.ediy.co.nz/vbjson-json-parser-library-in-vb6-xidc55680.html
Below is code I've pieced together from what I could find online and right now it pulls the values associated with each attribute in the row. But I only need to pull the contents of the "Value" portion and I can't seem to figure out how to do that. I think I really just need help with my for loop because I have the JSON, I have a library that appears to work, I am just struggling to figure out how to combine it all.
Dim xmlHttp As Object
Set xmlHttp = CreateObject("MSXML2.ServerXMLHTTP.6.0")
xmlHttp.Open "GET", URl, False
xmlHttp.setRequestHeader "Content-Type", "text/xml"
xmlHttp.send
Dim strDiv As String, startVal As Long, endVal As Long
strDiv = xmlHttp.ResponseText
startVal = InStr(1, strDiv, "rows", vbTextCompare)
endVal = InStr(startVal, strDiv, "]", vbTextCompare)
strDiv = "{" & Mid(strDiv, startVal - 1, (endVal - startVal) + 2) & "}"
Dim JSON As New JSON
Dim p As Object
Set p = JSON.parse(strDiv)
i = 1
For Each Item In p("rows")(1)("cells")(1)
Cells(2, i) = p("rows")(1)("cells")(1)(Item)
i = i + 1
Next
Ran into a similar problem, see my answer here: https://stackoverflow.com/a/16825736/1240745
This library has been a life-saver for me: https://github.com/VBA-tools/VBA-JSON (previously https://code.google.com/p/vba-json/)
I use it in a library I wrote for accessing Salesforce, Trello, and a few others. (Shameless plug): https://github.com/VBA-tools/VBA-Web
Using the VBA-JSON library, it would involve something like the following:
Dim Parsed As Dictionary
Set Parsed = JsonConverter.ParseJson(xmlHttp.ResponseText)
' Object -> Dictionary, so Row and Cell: Dictionary
Dim Row As Dictionary
Dim Cell As Dictionary
' Array -> Collection, so Parsed("rows"): Collection
For Each Row In Parsed("rows")
For Each Cell In Row("cells")
' Access Dictionary items by key
Cells(Row("rowNumber"), Cell("columnId")) = Cell("value")
Next Cell
Next Row
(or something similar)