VBA: Creating substrings out of JSON and reformatting into columns - json

I have information from a Facebook FQL Query in the form of JSON and pasted it into Excel. Here's a part of the result:
"data": [
{
"name": "Hilton Head Island - TravelTell",
"location": {
"street": "7 Office Way, Suite 215",
"city": "Hilton Head Island",
"state": "SC"
},
"fan_count": 143234,
"talking_about_count": 18234,
"were_here_count": 4196
},
{
"name": "Hilton Hawaiian Village Waikiki Beach Resort",
"location": {
"street": "2005 Kalia Road",
"city": "Honolulu",
"state": "HI"
},
"fan_count": 34072,
"talking_about_count": 4877,
"were_here_count": 229999
},
{
"name": "Hilton New York",
"location": {
"street": "1335 Avenue of the Americas",
"city": "New York",
"state": "NY"
},
"fan_count": 12885,
"talking_about_count": 969,
"were_here_count": 72206
},
I'm trying to use substrings to parse the data and then create columns on another worksheet using "name, street, city, state, fan_count, etc." as the column headers. I'm trying out code to do this for just "name:" right now but there's an error when it hits the line with documentText = myRange.Text . I can't figure out what the error is.
Another problem is that the strings contain quotations. For example, I want the SecondTerm to be ", but I get errors when I try to have it equal "","
Sub Substring_Test()
Dim nameFirstTerm As String
Dim nameSecondTerm As String
Dim myRange As Range
Dim documentText As String
Dim startPos As Long 'Stores the starting position of firstTerm
Dim stopPos As Long 'Stores the starting position of secondTerm based on first term's location
Dim nextPosition As Long 'The next position to search for the firstTerm
nextPosition = 1
'First and Second terms as defined by your example. Obviously, this will have to be more dynamic
'if you want to parse more than justpatientFirstname.
firstTerm = "name"": """
secondTerm = ""","""
'Get all the document text and store it in a variable.
Set myRange = Sheets("Sheet1").UsedRange
'Maximum limit of a string is 2 billion characters.
'So, hopefully your document is not bigger than that. However, expect declining performance based on how big doucment is
documentText = myRange.Text
'Loop documentText till you can't find any more matching "terms"
Do Until nextPosition = 0
startPos = InStr(nextPosition, documentText, firstTerm, vbTextCompare)
stopPos = InStr(startPos, documentText, secondTerm, vbTextCompare)
Debug.Print Mid$(documentText, startPos + Len(firstTerm), stopPos - startPos - Len(secondTerm))
nextPosition = InStr(stopPos, documentText, firstTerm, vbTextCompare)
Loop
Sheets("Sheet2").Range("A1").Value = documentText
End Sub

Sub Tester()
Dim json As String
Dim sc As Object
Dim o, loc, x, num
Set sc = CreateObject("scriptcontrol")
sc.Language = "JScript"
json = ActiveSheet.Range("a1").Value
'Debug.Print json
sc.Eval "var obj=(" & json & ")" 'evaluate the json response
'Add some accessor functions...
' get count of records returned
sc.AddCode "function getCount(){return obj.data.length;}"
' return a specific record (with some properties renamed)
sc.AddCode "function getItem(i){var o=obj.data[i];" & vbLf & _
"return {nm:o.name,loc:o.location," & vbLf & _
"f:o.fan_count,ta:o.talking_about_count," & vbLf & _
"wh:o.were_here_count};}"
num = sc.Run("getCount")
Debug.Print "#Items", num
For x = 0 To num - 1
Debug.Print ""
Set o = sc.Run("getItem", x)
Debug.Print "Name", o.nm
Debug.Print "Street", o.loc.street
Debug.Print "City", o.loc.city
Debug.Print "Street", o.loc.street
Debug.Print "Fans", o.f
Debug.Print "talking_about", o.ta
Debug.Print "were_here", o.wh
Next x
End Sub
Note: the javascript getItem function dosn't return a record directly, but wraps the data so that some of the JSON-drived property names are altered (specifically "name" and "location"). VBA seems to have a problem dealing with accessing properties on objects passed from javascript if the property name resembles a "regular" property like Name (or Location).

This should work although you may need to change some of the sheet names
Sub Test()
Dim vData() As Variant
Dim vHeaders As Variant
Dim vCell As Variant
Dim i As Long
vHeaders = Array("Name", "Street", "City", "State", "Fan Count", "Talking About Count", "Were Here Count")
i = 1
Do While i <= ActiveSheet.UsedRange.Rows.Count
If InStr(Cells(i, 1).Text, "{") Or _
InStr(Cells(i, 1).Text, "}") Or _
Cells(i, 1).Text = """data"": [" Or _
Cells(i, 1).Text = "" Then
Rows(i).Delete
Else
Cells(i, 1).Value = Replace(Cells(i, 1).Text, """", "")
Cells(i, 1).Value = Replace(Cells(i, 1).Text, ",", "")
Cells(i, 1).Value = WorksheetFunction.Trim(Cells(i, 1).Text)
i = i + 1
End If
Loop
i = 0
For Each vCell In Range(Cells(1, 1), Cells(ActiveSheet.UsedRange.Rows.Count, 1))
If InStr(vCell.Text, "name:") Then
i = i + 1
ReDim Preserve vData(1 To 7, 1 To i)
End If
If InStr(vCell.Text, "name") Then
vData(1, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
If InStr(vCell.Text, "street") Then
vData(2, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
If InStr(vCell.Text, "city") Then
vData(3, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
If InStr(vCell.Text, "state") Then
vData(4, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
If InStr(vCell.Text, "fan_count") Then
vData(5, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
If InStr(vCell.Text, "talking_about_count") Then
vData(6, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
If InStr(vCell.Text, "were_here_count") Then
vData(7, i) = Right(vCell.Text, Len(vCell.Text) - InStr(1, vCell.Text, ":"))
End If
Next
'Cells.Delete
Sheets("Sheet2").Select
Range(Cells(1, 1), Cells(UBound(vData, 2), UBound(vData))).Value = WorksheetFunction.Transpose(vData)
Rows(1).EntireRow.Insert
Range(Cells(1, 1), Cells(1, UBound(vHeaders) + 1)).Value = vHeaders
End Sub

I have no clue about the 1st part (not familiar with JSON at all), but regarding the 2nd one - try the following lines:
firstTerm = Chr(34) & "name: " & Chr(34)
secondTerm = Chr(34) & ","
Or simply - use Chr(34) for every double quote you want.

Related

Integration of web API into Excel using Macro & VBA

I have used link - Parsing JSON to Excel using VBA
to solve my problem, but it is not resolved fully.
Up to JSON Parse it is working as expected then not able to convert it into 2D Array & that's why not able convert JSON data into Excel table.
using code as below,
Option Explicit
Sub GetAPI_Data()
Dim sJSONString As String
Dim sJSONStringTmp1 As String
Dim sJSONStringTmp2 As String
Dim vJSON
Dim sState As String
Dim aData()
Dim aHeader()
' Retrieve JSON content
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://pastebin.com/raw/Zp0mFEqd", True
.send
Do Until .readyState = 4: DoEvents: Loop
sJSONString = "{ ""EmployeeDetails"": " + .responseText + "}"
Debug.Print sJSONString
End With
Debug.Print sJSONString
' Parse JSON sample
JSON.Parse sJSONString, vJSON, sState
If sState = "Error" Then MsgBox "Invalid JSON": End
' Convert JSON to 2D Array
JSON.toArray vJSON("EmployeeDetails"), aData, aHeader
' Output to worksheet #1
Output aHeader, aData, ThisWorkbook.Sheets(1)
MsgBox "Completed"
End Sub
Sub Output(aHeader, aData, oDestWorksheet As Worksheet)
With oDestWorksheet
.Activate
.Cells.Delete
With .Cells(1, 1)
.Resize(1, UBound(aHeader) - LBound(aHeader) + 1).Value = aHeader
.Offset(1, 0).Resize( _
UBound(aData, 1) - LBound(aData, 1) + 1, _
UBound(aData, 2) - LBound(aData, 2) + 1 _
).Value = aData
End With
.Columns.AutoFit
End With
End Sub
My JSON Data as follows,
{
"EmployeeDetails": "[{\"AccountName\":\"CWT COMMODITIES (ANTWERP) N.V.\",\"AccountOwner\":null,\"Age\":\"257\",\"AgreementLevel\":null,\"Amount\":\"1\",\"Amount_converted\":\"1.13\",\"Amount_converted_Currency\":null,\"AmountCurrency\":\"EUR\",\"CloseDate\":\"2022-06-15\",\"CloseMonth\":null,\"CoreTechnology\":null,\"CreatedDate\":\"2021-10-01T07:52:36.000+0000\",\"CustomerIndustry\":\"Infrastructure / Transport\",\"District\":null,\"ePSFBranch_Location\":null,\"ExclusiveHBSTechnology\":null,\"ExpectedProjectDuration\":null,\"FiscalPeriod_Num\":\"6\",\"FiscalYear\":\"2022\",\"ForecastCategory\":\"Pipeline\",\"FPXBranch\":null,\"GrossMargin_Percentage\":null,\"Industry\":\"Education\",\"IndustryCode\":null,\"LeadSource\":null,\"LegacyOpportunityNumber\":null,\"LineofBusiness\":null,\"NextSteps\":null,\"OpportunityName\":\"CWT Onderhoud BRANDDETECTIE\",\"OpportunityOwner\":\"Wim Hespel\",\"OpportunityType\":null,\"OwnerRole\":\"Direct EUR VSK&TTG Sales\",\"PrimarySolutionFamily\":null,\"PrimarySubSolutionFamily\":null,\"Probability_Percentage\":\"5\",\"ProjectEndDate\":\"2022-06-15\",\"ProjectStartDate\":\"2022-06-15\",\"RecordType\":\"Core\",\"Region\":\"Europe\",\"SalesRegion\":\"Belgium & Luxembourg\",\"Stage\":\"1.First Calls\",\"SubRegion\":\"HBS Benelux\",\"OpportunityNumber\":\"0001458471\",\"VerticalMarket\":\"Infrastructure / Transport excluding Airports\",\"Win_LossCategory\":null,\"Win_LossReason\":null,\"Country\":\"Belgium\",\"InitiatedCPQEstimateProcess\":\"False\",\"LastModifiedDate\":\"2022-03-17T15:27:33.000+0000\",\"LocationSS\":null,\"OpportunityCurrency\":null,\"OpportunityID\":\"0065a0000109AMQAA2\",\"OpportunitySubType\":null,\"OwnerID\":\"0051H00000AvuQ2QAJ\",\"RecordTypeId\":\"0121H000001eZ9VQAU\",\"CustomerType\":\"Existing Customer\",\"GBE\":\"HBS\",\"EditedBy\":\"\",\"Field_Or_Event\":\"\",\"OldValue\":\"\",\"NewValue\":\"\",\"EditDate\":\"\",\"LastStageChangeDate\":null,\"StageDuration\":null,\"ExpectedRevenue\":\"0.05\",\"GrossMarginAtSubmission\":null,\"LastActivity\":null,\"OwnerEID\":\"H185118\"}]"
}
Error:
1] on local machine I am getting error in JSON.toArray i.e. not able to create 2D array.
2] while using above code with online JSON Data as per URL then getting only 2 column data which is not proper.
Updated Code
Option Explicit
Sub GetAPI_Data()
Dim sJSONString As String
Dim sJSONStringTmp1 As String
Dim sJSONStringTmp2 As String
Dim vJSON
Dim s
Dim sState As String
Dim aData()
Dim aHeader()
' Retrieve JSON content
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://pastebin.com/raw/Zp0mFEqd", True
.send
Do Until .readyState = 4: DoEvents: Loop
'sJSONString = "{ ""EmployeeDetails"": " + .responseText + "}"
sJSONString = .responseText
Debug.Print sJSONString
End With
' Parse JSON sample
JSON.Parse sJSONString, vJSON, sState
If sState = "Error" Then MsgBox "Invalid JSON": End
Debug.Print vJSON.Item("EmployeeDetails")
'vJSON("EmployeeDetails") = "{ ""EmployeeDetails"": " + vJSON("EmployeeDetails") + "}"
s = vJSON("EmployeeDetails")
s = "{""data"":" & s & "}"
Debug.Print vJSON.Item("EmployeeDetails")
Dim xJSON As Dictionary
'JSON.Parse vJSON("EmployeeDetails"), xJSON, sState
JSON.Parse s, xJSON, sState
If sState = "Error" Then MsgBox "Invalid JSON": End
' Convert JSON to 2D Array
JSON.toArray xJSON, aData, aHeader
' Output to worksheet #1
Output aHeader, aData, ThisWorkbook.Sheets(1)
MsgBox "Completed"
End Sub
Sub Output(aHeader, aData, oDestWorksheet As Worksheet)
With oDestWorksheet
.Activate
.Cells.Delete
With .Cells(1, 1)
.Resize(1, UBound(aHeader) - LBound(aHeader) + 1).Value = aHeader
.Offset(1, 0).Resize( _
UBound(aData, 1) - LBound(aData, 1) + 1, _
UBound(aData, 2) - LBound(aData, 2) + 1 _
).Value = aData
End With
.Columns.AutoFit
End With
End Sub
Note : I have updated API with multiple line of JSON
Error:
1] Now I am getting required data.
2] But the main issue is, it is coming only in 2 rows (1 for column header & other one for Data)
3] Requirement is, it should display 5 different rows with first row of header
Please help me out from this.
This worked for me to give a 2D array which could be placed on a worksheet:
Sub Tester()
Dim json As Object, s As String, recs As Object, arr
Set json = ParseJson(GetContent("C:\Temp\json.txt")) 'reading from a file for testing
s = json("EmployeeDetails") 'get the embedded json
Set json = ParseJson("{""data"":" & s & "}") 'parse the embedded json
Set recs = json("data") 'collection of records 'a Collection of records
arr = RecsToArray(recs) 'convert to a 2D array
With Sheet6.Range("A1")
.Resize(UBound(arr, 1), UBound(arr, 2)).Value = arr 'write array to sheet
End With
End Sub
'Convert an array/collection of json objects (dictionaries)
' to a tabular 2D array, with a header row
Function RecsToArray(recs As Collection)
Dim rec, k, i As Long, r As Long, c As Long, arr()
Dim dictCols As Object
Set dictCols = CreateObject("scripting.dictionary")
i = 0
'Collect all field names (checking every record in case some may be either incomplete or contain "extra" fields)
' Assumes all field names are unique per record, and no nested objects/arrays within a record
For Each rec In recs
For Each k In rec
If Not dictCols.Exists(k) Then
i = i + 1
dictCols.Add k, i
End If
Next k
Next rec
'size the output array
ReDim arr(1 To recs.Count + 1, 1 To i)
'Populate the header row
For Each k In dictCols
arr(1, dictCols(k)) = k
Next k
r = 1
'collect the data rows
For Each rec In recs
r = r + 1 'next output row
For Each k In rec
arr(r, dictCols(k)) = rec(k)
Next k
Next rec
RecsToArray = arr
End Function
Function GetContent(f As String) As String
GetContent = CreateObject("scripting.filesystemobject"). _
OpenTextFile(f, 1).ReadAll()
End Function
The very first issue you have is that you put an additional { "EmployeeDetails" …json… } around your JSON that allready has this
sJSONString = "{ ""EmployeeDetails"": " + .responseText + "}"
Don't do that!
Second issue you have is that you have a string encoded JSON inside a JSON:
So your original JSON is:
{
"EmployeeDetails": "[{\"AccountName\":\"CWT COMMODITIES (ANTWERP) N.V.\",\"AccountOwner\":null,\"Age\":\"257\",\"AgreementLevel\":null,\"Amount\":\"1\",\"Amount_converted\":\"1.13\",\"Amount_converted_Currency\":null,\"AmountCurrency\":\"EUR\",\"CloseDate\":\"2022-06-15\",\"CloseMonth\":null,\"CoreTechnology\":null,\"CreatedDate\":\"2021-10-01T07:52:36.000+0000\",\"CustomerIndustry\":\"Infrastructure / Transport\",\"District\":null,\"ePSFBranch_Location\":null,\"ExclusiveHBSTechnology\":null,\"ExpectedProjectDuration\":null,\"FiscalPeriod_Num\":\"6\",\"FiscalYear\":\"2022\",\"ForecastCategory\":\"Pipeline\",\"FPXBranch\":null,\"GrossMargin_Percentage\":null,\"Industry\":\"Education\",\"IndustryCode\":null,\"LeadSource\":null,\"LegacyOpportunityNumber\":null,\"LineofBusiness\":null,\"NextSteps\":null,\"OpportunityName\":\"CWT Onderhoud BRANDDETECTIE\",\"OpportunityOwner\":\"Wim Hespel\",\"OpportunityType\":null,\"OwnerRole\":\"Direct EUR VSK&TTG Sales\",\"PrimarySolutionFamily\":null,\"PrimarySubSolutionFamily\":null,\"Probability_Percentage\":\"5\",\"ProjectEndDate\":\"2022-06-15\",\"ProjectStartDate\":\"2022-06-15\",\"RecordType\":\"Core\",\"Region\":\"Europe\",\"SalesRegion\":\"Belgium & Luxembourg\",\"Stage\":\"1.First Calls\",\"SubRegion\":\"HBS Benelux\",\"OpportunityNumber\":\"0001458471\",\"VerticalMarket\":\"Infrastructure / Transport excluding Airports\",\"Win_LossCategory\":null,\"Win_LossReason\":null,\"Country\":\"Belgium\",\"InitiatedCPQEstimateProcess\":\"False\",\"LastModifiedDate\":\"2022-03-17T15:27:33.000+0000\",\"LocationSS\":null,\"OpportunityCurrency\":null,\"OpportunityID\":\"0065a0000109AMQAA2\",\"OpportunitySubType\":null,\"OwnerID\":\"0051H00000AvuQ2QAJ\",\"RecordTypeId\":\"0121H000001eZ9VQAU\",\"CustomerType\":\"Existing Customer\",\"GBE\":\"HBS\",\"EditedBy\":\"\",\"Field_Or_Event\":\"\",\"OldValue\":\"\",\"NewValue\":\"\",\"EditDate\":\"\",\"LastStageChangeDate\":null,\"StageDuration\":null,\"ExpectedRevenue\":\"0.05\",\"GrossMarginAtSubmission\":null,\"LastActivity\":null,\"OwnerEID\":\"H185118\"}]"
}
and what you get out of vJSON.Item("EmployeeDetails") is
[
{
"AccountName": "CWT COMMODITIES (ANTWERP) N.V.",
"AccountOwner": null,
"Age": "257",
"AgreementLevel": null,
"Amount": "1",
"Amount_converted": "1.13",
"Amount_converted_Currency": null,
"AmountCurrency": "EUR",
"CloseDate": "2022-06-15",
"CloseMonth": null,
"CoreTechnology": null,
"CreatedDate": "2021-10-01T07:52:36.000+0000",
"CustomerIndustry": "Infrastructure / Transport",
"District": null,
"ePSFBranch_Location": null,
"ExclusiveHBSTechnology": null,
"ExpectedProjectDuration": null,
"FiscalPeriod_Num": "6",
"FiscalYear": "2022",
"ForecastCategory": "Pipeline",
"FPXBranch": null,
"GrossMargin_Percentage": null,
"Industry": "Education",
"IndustryCode": null,
"LeadSource": null,
"LegacyOpportunityNumber": null,
"LineofBusiness": null,
"NextSteps": null,
"OpportunityName": "CWT Onderhoud BRANDDETECTIE",
"OpportunityOwner": "Wim Hespel",
"OpportunityType": null,
"OwnerRole": "Direct EUR VSK&TTG Sales",
"PrimarySolutionFamily": null,
"PrimarySubSolutionFamily": null,
"Probability_Percentage": "5",
"ProjectEndDate": "2022-06-15",
"ProjectStartDate": "2022-06-15",
"RecordType": "Core",
"Region": "Europe",
"SalesRegion": "Belgium & Luxembourg",
"Stage": "1.First Calls",
"SubRegion": "HBS Benelux",
"OpportunityNumber": "0001458471",
"VerticalMarket": "Infrastructure / Transport excluding Airports",
"Win_LossCategory": null,
"Win_LossReason": null,
"Country": "Belgium",
"InitiatedCPQEstimateProcess": "False",
"LastModifiedDate": "2022-03-17T15:27:33.000+0000",
"LocationSS": null,
"OpportunityCurrency": null,
"OpportunityID": "0065a0000109AMQAA2",
"OpportunitySubType": null,
"OwnerID": "0051H00000AvuQ2QAJ",
"RecordTypeId": "0121H000001eZ9VQAU",
"CustomerType": "Existing Customer",
"GBE": "HBS",
"EditedBy": "",
"Field_Or_Event": "",
"OldValue": "",
"NewValue": "",
"EditDate": "",
"LastStageChangeDate": null,
"StageDuration": null,
"ExpectedRevenue": "0.05",
"GrossMarginAtSubmission": null,
"LastActivity": null,
"OwnerEID": "H185118"
}
]
Which you will need to parse again because this still is JSON!
But the converter you use does not accept the JSON to start with [ and thats another issue here. Because if I strip that brackets off so the [ ] in the beginning and end are gone and parse that again it will work:
Sub GetAPI_Data()
Dim sJSONString As String
Dim sJSONStringTmp1 As String
Dim sJSONStringTmp2 As String
Dim vJSON As Dictionary
Dim sState As String
Dim aData()
Dim aHeader()
' Retrieve JSON content
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "https://pastebin.com/raw/Zp0mFEqd", True
.send
Do Until .readyState = 4: DoEvents: Loop
'sJSONString = "{ ""EmployeeDetails"": " + .responseText + "}" 'don't do this!
sJSONString = .responseText
End With
Debug.Print sJSONString
' Parse JSON sample
JSON.Parse sJSONString, vJSON, sState
If sState = "Error" Then MsgBox "Invalid JSON": End
Debug.Print vJSON.Item("EmployeeDetails")
Dim StripOffOuterBrackets As String
StripOffOuterBrackets = Mid(vJSON.Item("EmployeeDetails"), 2, Len(vJSON.Item("EmployeeDetails")) - 2)
Debug.Print StripOffOuterBrackets
Dim xJSON As Dictionary
JSON.Parse StripOffOuterBrackets, xJSON, sState
If sState = "Error" Then MsgBox "Invalid JSON": End
' Convert JSON to 2D Array
JSON.ToArray xJSON, aData, aHeader
' Output to worksheet #1
Output aHeader, aData, ThisWorkbook.Sheets(1)
MsgBox "Completed"
End Sub
And it outputs the following (and some more lines)

Accessing JSON array data in VBA

I have this json array and the data I need is in the array that starts data_id which I cannot extract. I am able to extract keys,value before the array but not in the array. I believe I need to request data in a specific way with a number in () after the fieldname nest but I cannot find a beginners explanation to see what number goes in the brackets and why you chose that number.
{"api":{"results":37,"data":[{"data_id":643951,"location_id":3005,"person":{"name":"Bob","country":"Turkey",
Any tips appreciated here is some code
'Print a few object variables before parse
Dim WrkSht As Worksheet
Set WrkSht = ThisWorkbook.Worksheets("jsonoutput")
WrkSht.Cells(1, 1).Value = xml_obj.responseText
' Displays data fine in one string as shown above
'Parse the response
Set jp = JsonConverter.ParseJson(xml_obj.responseText)
For Each dict In jp
Debug.Print dict
If Not IsObject(jp(dict)) Then
Debug.Print jp(dict)
Else
For Each subDict In jp(dict)
Debug.Print subDict
'Debug.Print jp(dict)(subDict)
Next subDict
End If
Next dict
' I need to drill down into further levels but ?
End Sub
Here's a simple example
JSON used:
{"api":{"results":37,
"data":[{"data_id":643951,
"location_id":3005,
"person":{"name":"Bob","country":"Turkey"}
} ]
}}
Code:
Sub Test36()
Dim jso As Object, arr, data, obj
'loading from a cell for testing...
Set jso = JsonConverter.ParseJson(Sheet2.Range("A17").Value)
'jso is a Dictionary
Debug.Print jso("api")("results") '>> 37
Set data = jso("api")("data") 'data is a Collection
Debug.Print data.Count ' >> 1
For Each obj In data
Debug.Print obj("data_id") '>> 643951
Debug.Print obj("person")("country") '>> Turkey
Next obj
End Sub
I thought I would just share the code I ended up with. It can be improved on and some is over coded simply to make it easier to see where amendments can be made. Currently this will:
Access an API - just put as many header lines in as you need
Collect the JSON data and flatten it to one level - this code will only work with Json where
blank values are recorded as "null" rather than just "". You may
have to manually correct the columns (or update the code) for
blank values
Ask you which key you want to start with - it will
then mark that keys values to start a new row each time it comes
across this
Make replacements in the data to create delimiters
to mark which data is keys and which is values
Pastes your keys
in row 1 that have values - dictionary keys are ignored but
you can change that if needed
Remove all keys from the string to
just leave values and paste those in the rows below.
You need to have the RunScriptime XMl HTTP 6.0 and Object library ticked in Tools reference in VBA as well
Sub FlattenJsonGetDataFromKeysWithValues()
ActiveWorkbook.Worksheets("yourworksheet").Range("a1:ZZ10000").ClearContents
Dim i As Long
'Declare variables
Dim xml_obj As MSXML2.XMLHTTP60
Dim base_url As String
Dim Json1 As String, Json2, Json3, Json4, Json5, Json6, json7, Json8, Json9, Json10, Json11, Json12, Json13, Json14, Json15, Json16, Json17, Json18
Dim Json0 As String
Dim keys As String, keys2
'Create a new Request Object make sure in Tools-> reference the xml6.0, scripting runtime and object library are ticked
Set xml_obj = New MSXML2.XMLHTTP60
'Define URL Components two headers are shown but you cana dd as many as required
base_url = "https://yoururl.com"
xml_obj.Open "GET", base_url
xml_obj.SetRequestHeader "key", "55555"
xml_obj.SetRequestHeader "host", "valuefor2ndheaderkeyifneeded"
xml_obj.Send
'Print the status code in case something went wrong
MsgBox("The Request was " + CStr(xml_obj.Status))
strJson0 = xml_obj.responseText
MsgBox (Len(strJson0)) ' tells how long string is
'Look for Json current delimiters and change all to a comma
Json1 = strJson0
Const SpecialCharacters As String = "!,#,#,$,%,^,&,*,(,),{,[,],},?,:"
Dim char As Variant
For Each char In Split(SpecialCharacters, ",")
Json1 = Replace(Json1, char, " ")
Next
' Place # before all field names, I have shown in this way so if needed you can vary to suit your needs
Json2 = Replace(Json1, "," & Chr(34), "#") ' Replaces ," - Chr(34) is a "
Json3 = Replace(Json2, ", " & Chr(34), "#") ' replaces , "
Json4 = Replace(Json3, Chr(34) & " " & Chr(34), Chr(34) & "#") ' Replaces " "
Json5 = Replace(Json4, Chr(34) & " " & Chr(34), Chr(34) & "#") ' Replaces " "
'Place : after fieldname and before value
Json6 = Replace(Json5, Chr(34) & " " & Chr(34), ":") 'Replaces " "
json7 = Replace(Json6, Chr(34) & " ", ":") 'Replaces "(blankspace)
Json8 = Replace(json7, Chr(34), ":") 'Replaces "
Json9 = Replace(Json8, ":#", "#") 'Replaces :# with #
Json10 = Replace(Json9, "/", "") 'Removes /
Json11 = Replace(Json10, " ", "") 'Removes blankspace
If Left(Json11, 1) = ":" Then Json11 = "#" & Right(Json11, (Len(Json11) - 1)) ' Replace : with # if first character
' Now you just have field names (keys) marked by # and values marked by :
' Find Field Names - which field should we start with? How many times is that key in the data
Dim firstkey As String
MsgBox (Json11) 'View this to see your key/header row options
firstkey = InputBox("Enter First Field/Key to locate") 'This will mark where all new rows start
keys = Json11
' Now take text between #*# as dictionary keys and ignore and text between #*: as headers for field names until repeat text is found in string by finding the firstkey you input above and putting a # marker in all heading that = firstkey
Dim openPos As Long
Dim closePos As Long
Dim k As Integer
Dim jsonFields As Collection
Set jsonFields = New Collection
Dim jsonValues As Collection
Set jsonValues = New Collection
' Find wanted starting key, skip over keys without value
k = 1
openPos = InStr(keys, firstkey)
closePos = InStr(openPos, keys, ":")
If InStr(1, Mid(keys, openPos, closePos - openPos), "#") > 0 Then openPos = openPos + InStr(1, Mid(keys, openPos, closePos - openPos), "#")
jsonFields.Add Mid(keys, openPos, closePos - openPos)
keys = Replace(keys, firstkey & ":", ":#")
k = k + 1
' Find other keys with values, find dict keys
Do Until Mid(keys, openPos, closePos - openPos) = ""
openPos = InStr(closePos, keys, "#") + 1
If k = 2 Then openPos = InStr(1, keys, "#")
closePos = InStr(openPos, keys, ":")
If InStr(1, Mid(keys, openPos, closePos - openPos), "#") > 0 Then openPos = openPos + InStr(1, Mid(keys, openPos, closePos - openPos), "#")
jsonFields.Add Mid(keys, openPos, closePos - openPos)
k = k + 1
Loop
' Find values and remove delimiters, keys and replace : in https values that are removed with other delimiters
y = 2 ' use to start populate rows
currentcolumn = 1
Dim r&
p = Split(keys, "#")
For r = 0 To UBound(p)
If InStr(1, p(r), ":") Then p(r) = Right(p(r), Len(p(r)) - InStr(1, p(r), ":") + 1) ' remove keys
If InStr(1, p(r), ":") = 0 Then p(r) = "" ' remove :
If InStr(1, p(r), ":") Then p(r) = Right(p(r), Len(p(r)) - InStr(1, p(r), ":")) ' set value for collection to print later
If InStr(1, p(r), "https") Then p(r) = Replace(p(r), "https", "https:") ' fix https value by readding :
jsonValues.Add p(r) ' add to collection
Next r
' Print Values to worksheet
currentcolumn = 1
'Now Output your parsed key data, turn screen updating off
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For Each Item In jsonFields
ActiveWorkbook.Worksheets("yourworksheet").Cells(1, currentcolumn).Value = Item
currentcolumn = currentcolumn + 1
Next Item
y = 2
currentcolumn = 1
Dim ws As Worksheet
Set ws = Worksheets("yourworksheet")
For Each Item In jsonValues
If Len(Item) > 0 Then
If InStr(1, Item, "#") = 1 Then
y = y + 1
currentcolumn = 1
End If
ws.Cells(y, currentcolumn).Value = Item
currentcolumn = currentcolumn + 1
End If
Next Item
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Error 1004 when pulling table with ActiveWorkbook.Queries.Add

I'm trying to create a quick way to pull financial statements (a basic table) from yahoo finance (e.g. https://finance.yahoo.com/quote/FB/financials?p=FB) with VBA.
I'm a complete noob so I used the record macro tool and Get data from web and tried (with my non-existent VBA knowledge) to adapt it to use a variable (Ticker) to change the company.
When using the get data from web function the table is imported perfectly but it doesn't work with VBA code. I get 1004 error about either ListObject.DisplayName or Refresh BackgroundQuery
Sub Macro5()
Dim Ticker As String
Ticker = InputBox("Ticker")
ActiveWorkbook.Queries.Add Name:="Table" & Ticker, Formula:= _
"let" & Chr(13) & "" & Chr(10) & " Source = Web.Page(Web.Contents(""https://finance.yahoo.com/quote/& Ticker &/financials?p=&Ticker &""))," & Chr(13) & "" & Chr(10) & " Data2 = Source{2}[Data]," & Chr(13) & "" & Chr(10) & " #""Type modifié"" = Table.TransformColumnTypes(Data2,{{""Column1"", type text}, {""Column2"", type text}, {""Column3"", type text}, {""Column4"", type text}, {""Column5"", type text}})" & Chr(13) & "" & Chr(10) & "in" & Chr(13) & "" & Chr(10) & " #""Type modifié"""
ActiveWorkbook.Worksheets.Add
With ActiveSheet.ListObjects.Add(SourceType:=0, Source:= _
"OLEDB;Provider=Microsoft.Mashup.OleDb.1;Extended Properties=""""" _
, Destination:=Range("$A$1")).QueryTable
.CommandType = xlCmdSql
.CommandText = Array("SELECT * FROM [Table & Ticker")
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.BackgroundQuery = True
.RefreshStyle = xlInsertDeleteCells
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = True
.RefreshPeriod = 0
.PreserveColumnInfo = True
.ListObject.DisplayName = "Table" & Ticker
.Refresh BackgroundQuery:=False
End With
End Sub
The idea is to output the Income Statement form for the "Ticker" (FB in my exemple).
I'm using Excel 365 on windows
Thanks a lot
An easy method is to grab all the table elements on the page and loop those using clipboard to copy paste to sheet. You can adapt to write to different sheets based on ticker value. Use a loop over tickers to retrieve data but ensuring you create the ie object before the loop and then have the navigate2 within the loop so as to visit each new ticker page.
Public Sub GetTables()
Dim clipboard As Object, ws As Worksheet, j As Long, tables As Object
Dim ie As Object, ticker As String
ticker = "FB"
Set ws = ThisWorkbook.Worksheets("Sheet1")
ws.Cells.UnMerge
ws.Cells.ClearContents
Set clipboard = GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
Application.ScreenUpdating = False
Set ie = CreateObject("InternetExplorer.Application")
With ie
.Visible = True
.Navigate2 "https://finance.yahoo.com/quote/FB/financials?p=" & ticker
While .Busy Or .readyState < 4: DoEvents: Wend
Set tables = .document.querySelectorAll("table")
For j = 0 To tables.Length - 1
clipboard.SetText tables.item(j).outerHTML
clipboard.PutInClipboard
ws.Cells(LastRow(ws) + 2, 1).PasteSpecial
Next
.Quit
End With
Application.ScreenUpdating = True
End Sub
'https://www.rondebruin.nl/win/s9/win005.htm
Public Function LastRow(ByVal sh As Worksheet) As Long
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
This second method is rather a leap in knowledge for you, but maybe useful in future and to other readers. You can extract all the info on the page from a script tag. With some string splitting on the innerHTML of that script element you can get a string which a json parser can handle. You can then parse the json for whatever info you want. I include an outline only below.
Option Explicit
'VBE > Tools > References:
' Microsoft Internet Controls
' Microsoft Scripting Runtime
'Download and add in jsonconverter.bas from https://github.com/VBA-tools/VBA-JSON/blob/master/JsonConverter.bas
Public Sub GetYahooData()
Dim IE As New InternetExplorer, ticker As String
ticker = "FB"
With IE
.Visible = True
.Navigate2 "https://finance.yahoo.com/quote/FB/financials?p=" & ticker
While .Busy Or .readyState < 4: DoEvents: Wend
Dim script As Object, scripts As Object, i As Long, extract As String, json As Object
Set scripts = .document.querySelectorAll("script")
For i = 0 To scripts.Length - 1
If InStr(1, scripts.item(i).innerHTML, "/* -- Data -- */") Then
Set script = scripts.item(i)
Exit For
End If
Next
If Not script Is Nothing Then
extract = Split(Split(script.innerHTML, "root.App.main = ")(1), "(this));")(0)
extract = Left$(extract, InStrRev(extract, ";") - 1)
Set json = JsonConverter.ParseJson(extract)("context")("dispatcher")("stores")("QuoteSummaryStore")("cashflowStatementHistory")
End If
If Not json Is Nothing Then
'parse json for data of interest
End If
Stop ' <== Delete me later
.Quit
End With
End Sub
There is simply too much info in the json to go through it all but here is a snapshot extract of the webpage on the left, and the json which relates to it on the right:
I tried to workout solution on your adopted code. Your table of interest is Table 2 on the page when we retrieve the URL through Excel Data Tab from web. We have to tackle two problems .
Table reference is proper. As we run the program number of times query table name is in Excel memory and is not removed even after deleting the sheet. So I have to increment table index like [Table 2 (2)] then next time [Table 2 (3)] at 3 places in the code. If we increment table index every time program will run correctly.To find out what is the index number of table ListTables() sub routine will help. I could not find a suitable way that Excel do not remember table index of a deleted sheet table.
Second necessary point is to close connection. I have added suitable code for the same.
Final code works out as following.
Sub Macro7()
'
' Macro1 Macro
'
'
Dim Cn As Variant
Dim Ticker As String
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Ticker = InputBox("Ticker")
ActiveWorkbook.Queries.Add Name:="Table 2 (18)", Formula:= _
"let" & Chr(13) & "" & Chr(10) & " Source = Web.Page(Web.Contents(""https://finance.yahoo.com/quote/" & Ticker & "/financials?p=" & Ticker & """))," & Chr(13) & "" & Chr(10) & " Data2 = Source{2}[Data]," & Chr(13) & "" & Chr(10) & " #""Changed Type"" = Table.TransformColumnTypes(Data2,{{""Column1"", type text}, {""Column2"", type text}, {""Column3"", type text}, {""Column4"", type text}, {""Column5"", type text}})" & Chr(13) & "" & Chr(10) & "in" & Chr(13) & "" & Chr(10) & " #""Changed Type"""
ActiveWorkbook.Worksheets.Add
With ActiveSheet.ListObjects.Add(SourceType:=0, Source:= _
"OLEDB;Provider=Microsoft.Mashup.OleDb.1;Data Source=$Workbook$;Location=""Table 2 (2)"";Extended Properties=""""" _
, Destination:=Range("$A$1")).QueryTable
.CommandType = xlCmdSql
.CommandText = Array("SELECT * FROM [Table 2 (18)]")
.RowNumbers = False
.FillAdjacentFormulas = False
.PreserveFormatting = True
.RefreshOnFileOpen = False
.BackgroundQuery = True
.RefreshStyle = xlInsertDeleteCells
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = True
.RefreshPeriod = 0
.PreserveColumnInfo = True
.ListObject.DisplayName = "Table_2__18"
.Refresh BackgroundQuery:=False
End With
'Range("A16").Select
For Each Cn In ThisWorkbook.Connections
Cn.Delete
Next Cn
For Each Cn In ActiveSheet.QueryTables
Cn.Delete
Next Cn
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
And code routine for listing table index is:
Sub ListTables()
Dim xTable As ListObject
Dim xSheet As Worksheet
Dim I As Long
I = -1
Sheets.Add.Name = "Table Name"
For Each xSheet In Worksheets
For Each xTable In xSheet.ListObjects
I = I + 1
Sheets("Table Name").Range("A1").Offset(I).Value = xTable.Name
Sheets("Table Name").Range("B1").Offset(I).Value = xSheet.Name
Next xTable
Next
End Sub

Using VBA and VBA-JSON to access JSON data from Wordpress API

I'm building a VBA app that creates and modifies Wordpress website pages using resources scraped from the web. The Wordpress API returns a JSON file but there is no native support for parsing JSON in VBA so I imported VBA-JSON from GitHub. Here is the subroutine:
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
And the function it calls.
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
This code returns an Object/Collection with 1 item that contains a Variant/Object/Dictionary with 24 items but I'm lost on how to access these items. Here is a screenshot:
If I use the immediate window to query ?json.count I get the correct result "1" but after about six hours of researching on the web and trying as many variants as I could find, I'm still stuck on how to access the other 24.
Here is the 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’re an industry expert. <\/p>\n<p>Use your company’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’re…<\/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}]}}]
At the end of the day, I want to be able to spin up a few hundred pages of WP content extracted and collated from several internet sources and keep them up to date using this app. Further suggestions beyond the problem here would also be useful so long as we don't get outside of VBA.
The JsonConverter is returning a collection of VBA.Collections Scripting.Dictionaries, and Values. In order to understand the output you will have to test the TypeName of all the returned values.
The real question is "How to navigate through a json object (or any unknown object for that matter) and access the values within.
Immediate Window
Using the Immediate Window and the json object from the OP's post I will try to describe the thought process (in the style of the must read book: The Little Schemer)
' 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
Of course this process can get tedious consider the amount of nesting in this JSON Object.
JSON(1)("_links")("curies")(1)("templated")
Collection|Dictionary|Dictionary|Collection|Boolean Value
So I guess the best thing to do is write a function that will print all the accessor to the Immediate Window and go from there.
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
Usage:
PrintJSONAccessors JSON, "?JSON"
It appears that the MSScriptControl.ScriptControl only works on 32 bit systems. I guess that is what SIM was alluding to in his comments. Although, my answer is IMO correct, you should ignore the next section of comments.
FYI: I posted a function that parses the JSON into Arrays and Dictionaries Function to Return a JSON Like Objects Using VBA Collections and Arrays on Code Review. It is not a replacement for JsonConverter or omegastripes's JSON.Bas. It demonstrates that you can add JScript code to CreateObject("MSScriptControl.ScriptControl") and use it to process the JSON.
Try the code:
Set json = JsonConverter.ParseJson(s)
For Each k In json(1)
Debug.Print k & vbTab & json(1)(k)
Next
UPDATE
Take a look at the below example. Import JSON.bas module into the VBA project for JSON processing.
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
BTW, the similar approach applied in other answers.

Parsing JSON (US BLS) in VBA from MS Access, update

So, I had previously asked a question that was successfully answered (here: Parsing JSON (US BLS) in VBA from MS Access)
The new response I get that differs from the original question is that I've added a request to capture calculations. I've tried adding as a collection and scripting dictionary like footnotes but I see the format is not quite the same, and therefore I think it results in null when I try to gather the 1,3,6 and 12 month changes. I'd like to have some help figuring out how to capture those changes in the following response:
{
"status":"REQUEST_SUCCEEDED",
"responseTime":64,
"message":["BLS does not produce net change calculations for Series WPU381103"],
"Results":
{
"series":
[
{
"seriesID":"WPU381103",
"data":
[
{
"year":"2014",
"period":"M12",
"periodName":"December",
"value":"98.9",
"footnotes":
[
{
"code":"P",
"text":"Preliminary. All indexes are subject to revision four months after original publication."
}
],
"calculations":
{
"net_changes":{},
"pct_changes":
{
"1":"0.0",
"3":"0.1",
"6":"0.0",
"12":"-0.7"
}
}
},
{
"year":"2014",
"period":"M11",
"periodName":"November",
"value":"98.9",
"footnotes":
[
{
"code":"P",
"text":"Preliminary. All indexes are subject to revision four months after original publication."
}
],
"calculations":
{
"net_changes":{},
"pct_changes":
{
"1":"0.1",
"3":"-0.4",
"6":"0.0",
"12":"-0.7"
}
}
},...
You will notice that there is a part now that says calculations, and seperates values by net changes, and percent changes. I am trying to get the percent changes within the "1", "3", "6" and "12" data items.
Here is the current code I'm that does NOT find calculations but captures all the other data:
response = http.responseText
jsonSource = response
I = 0
Dim jsonData As Scripting.Dictionary
Set jsonData = JSON.parse(jsonSource)
Dim responseTime As String
responseTime = jsonData("responseTime")
Dim results As Scripting.Dictionary
On Error Resume Next
Set results = jsonData("Results")
Dim series As Collection
On Error Resume Next
Set series = results("series")
Dim seriesItem As Scripting.Dictionary
For Each seriesItem In series
Dim seriesId As String
seriesId = seriesItem("seriesID")
Dim Data As Collection
Set Data = seriesItem("data")
Dim dataItem As Scripting.Dictionary
For Each dataItem In Data
Dim Year As String
Year = dataItem("year")
I = 1 + I
Dim Period As String
Period = dataItem("period")
Dim periodName As String
periodName = dataItem("periodName")
Dim Value As String
Value = dataItem("value")
Dim footnotes As Collection
Set footnotes = dataItem("footnotes")
Dim footnotesItem As Scripting.Dictionary
For Each footnotesItem In footnotes
Dim Code As String
Code = footnotesItem("code")
Dim text As String
text = footnotesItem("text")
Next footnotesItem
Next dataItem
Next seriesItem
Pretty straight forward. Remember, the JSON module implements JavaScript arrays as collections and objects as Scripting.Dictionary instances.
In your context, [..].calculations, [..].calculations.net_changes and [..].calculations.pct_changes are all objects so they are all converted to Dictionary objects.
So in your code, after the For Each footnotesItem In footnotes: [..]: Next footnotesItem block (therefore, above & before the Next dataItem line), you could add the following lines:
Dim calculations As Scripting.Dictionary
Dim sIndent As String
Dim calcNetChanges As Scripting.Dictionary
Dim calcPctChanges As Scripting.Dictionary
Dim varItem As Variant
Set calculations = dataItem("calculations")
sIndent = String(4, " ")
Set calcNetChanges = calculations("net_changes")
Debug.Print Year & ", " & Period & " (" & periodName & ") - Net Changes:"
If calcNetChanges.Count > 0 Then
For Each varItem In calcNetChanges.keys
Debug.Print sIndent & CStr(varItem) & ": " & calcNetChanges.Item(varItem)
Next varItem
Else
Debug.Print sIndent & "(none)"
End If
Set calcPctChanges = calculations("pct_changes")
Debug.Print Year & ", " & Period & " (" & periodName & ") - Pct Changes:"
If calcPctChanges.Count > 0 Then
For Each varItem In calcPctChanges.keys
Debug.Print sIndent & CStr(varItem) & ": " & calcPctChanges.Item(varItem)
Next varItem
Else
Debug.Print sIndent & "(none)"
End If
which, with the json data provided, should output something like this:
2014, M12 (December) - Net Changes:
(none)
2014, M12 (December) - Pct Changes:
1: 0.0
3: 0.1
6: 0.0
12: -0.7
2014, M11 (November) - Net Changes:
(none)
2014, M11 (November) - Pct Changes:
1: 0.1
3: -0.4
6: 0.0
12: -0.7
If you want to access the items of calculations.net_changes and calculations.pct_changes directly by their keys (known in advance), you would replace the two For Each varItem blocks by, respectively:
If calcNetChanges.Exists("1") Then Debug.Print "1: " & calcNetChanges.Item("1")
If calcNetChanges.Exists("3") Then Debug.Print "3: " & calcNetChanges.Item("3")
If calcNetChanges.Exists("6") Then Debug.Print "6: " & calcNetChanges.Item("6")
If calcNetChanges.Exists("12") Then Debug.Print "12: " & calcNetChanges.Item("12")
[..]
If calcPctChanges.Exists("1") Then Debug.Print "1: " & calcPctChanges.Item("1")
If calcPctChanges.Exists("3") Then Debug.Print "3: " & calcPctChanges.Item("3")
If calcPctChanges.Exists("6") Then Debug.Print "6: " & calcPctChanges.Item("6")
If calcPctChanges.Exists("12") Then Debug.Print "12: " & calcPctChanges.Item("12")
Finally, you should note that in the json data you're giving as the example, percentages (i.e. values of items for [..].calculations.net_changes & [..].calculations.pct_changes) are provided as strings, therefore you would probably want to convert those in Double (or Single) data using Val() to perform math or other numerical operations on them, e.g.:
Dim pctChange_1 As Double, pctChange_3 As Double
Dim pctChange_6 As Double, pctChange_12 As Double
pctChange_1 = 0#
pctChange_3 = 0#
pctChange_6 = 0#
pctChange_12 = 0#
If calcPctChanges.Exists("1") Then pctChange_1 = CDbl(Val(calcPctChanges.Item("1")))
If calcPctChanges.Exists("3") Then pctChange_3 = CDbl(Val(calcPctChanges.Item("3")))
If calcPctChanges.Exists("6") Then pctChange_6 = CDbl(Val(calcPctChanges.Item("6")))
If calcPctChanges.Exists("12") Then pctChange_12 = CDbl(Val(calcPctChanges.Item("12")))
Declare calculations as a Scripting.Dictionary and its pct-changes as Scripting.Dictionary as well. Append the following code snippet after the code for footnotes. HTH
Dim calculations As Scripting.Dictionary
Set calculations = dataItem("calculations")
Dim pct_changes As Scripting.Dictionary
Set pct_changes = calculations("pct_changes")
Dim pct_change As Variant
For Each pct_change In pct_changes
Debug.Print pct_change & ":" & pct_changes(pct_change)
Next pct_change
The Debug.Print pct_change & ":" & pct_changes(pct_change) produces the following result for the first calculations set:
1:0.0
3:0.1
6:0.0
12:-0.7