Extract JSON in Excel VBA - json

I want to parse stock quotes from the Robin Hood API via Excel VBA.
Say I want Amazon, which is https://api.robinhood.com/quotes/?symbols=AMZN.
Which produces:
{
"results":[
{
"ask_price":"1592.3900",
"ask_size":100,
"bid_price":"1591.0000",
"bid_size":500,
"last_trade_price":"1592.3900",
"last_extended_hours_trade_price":"1592.0000",
"previous_close":"1600.1400",
"adjusted_previous_close":"1600.1400",
"previous_close_date":"2018-05-07",
"symbol":"AMZN",
"trading_halted":false,
"has_traded":true,
"last_trade_price_source":"consolidated",
"updated_at":"2018-05-08T23:58:44Z",
"instrument":"https://api.robinhood.com/instruments/c0bb3aec-bd1e-471e-a4f0-ca011cbec711/"
}
]
}
Using an example like this answer, I have installed VBA-JSON and turned on Microsoft Scripting Runtime.
My code:
Public Sub STOCKQUOTE()
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
Const sURL As String = "https://api.robinhood.com/quotes/?symbols=AMZN"
http.Open "GET", sURL, False
http.send
Dim jsonResponse As Dictionary
Set jsonResponse = JsonConverter.ParseJson(http.responseText)
Dim results As String
Set results = jsonResponse("results")
MsgBox results
End Sub
But this doesn't work, instead I get Compiler Error: Object Required for the line Set results = jsonResponse("results").
If I add Debug.Print http.responseText I see the correct JSON, but any idea what I'm doing wrong?
VBA-JSON is installed correctly, because if I use their example, it works fine:
Dim Json As Object
Set Json = JsonConverter.ParseJson("{""a"":123,""b"":[1,2,3,4],""c"":{""d"":456}}")
But if I try changing Dictionary to Object, I get Run-time error '450': Wrong number of arguments or invalid property assignment.

Your json has an object called results. There could be, but isn't, multiple result objects. You have only one, so I think it's leading to confusion. Each result is going to get it's own entry in your jsonResponse dictionary. The ITEM in that dictionary will, itself, be a dictionary.
The best way to deal with iterating through the dictionary in a dictionary is to declare a new dictionary, I'm calling att for "Attributes" and then fill that dictionary with each iteration through the jsonResponse dictionary. It will only iterate once though as you only have one result:
Public Sub STOCKQUOTE()
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
Const sURL As String = "https://api.robinhood.com/quotes/?symbols=AMZN"
http.Open "GET", sURL, False
http.send
Dim jsonResponse As Dictionary
Set jsonResponse = JsonConverter.ParseJson(http.responseText)
Dim att As Dictionary
For Each att In jsonResponse("results")
Debug.Print att("last_trade_price")
Next att
End Sub
Alternatively, because you have only a single result, you could just refer to that result by it's index in the jsonResponse dictionary and then it's attribute you are after. This makes the code smaller, but if you ever get more than one result from your REST query it will be lost forever. No biggie though since you don't expect that to happen:
Public Sub STOCKQUOTE()
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
Const sURL As String = "https://api.robinhood.com/quotes/?symbols=AMZN"
http.Open "GET", sURL, False
http.send
Dim jsonResponse As Dictionary
Set jsonResponse = JsonConverter.ParseJson(http.responseText)
MsgBox (jsonResponse("results")(1)("last_trade_price"))
End Sub

Related

Extracting specific JSON field from .responseText to single excel cell

I am trying to retrieve a particular field, resolve, from JSON. I am not sure as to how I can go about getting that one field. I added the Msgbox [Exists & Fail] to see if the code is able to read the word resolve within the cell, however i am returned with fail.
Is there any way i can get only the field resolve? Kindly assist.
Thank you!
TargetURL = "https://api.passivetotal.org/v2/dns/passive?query=passivetotal.org"
actionType = "Content-Type"
actionWord = "application/json"
With CreateObject("Microsoft.XMLHTTP")
.Open "GET", TargetURL, False
.setRequestHeader actionType, actionWord
.setRequestHeader "Authorization", "Basic <Encoded 64>"
.send
If .Status = 200 Then
Sheets(6).Cells(Count, 10).Value = "Connected"
Debug.Print .responseText
MsgBox .responseText
Set JSON = ParseJson(.responseText)
Sheets(6).Cells(Count, 8).Value = .responseText
If Sheets(6).Cells(Count, 8).Value = ("resolve") Then
MsgBox ("Exists")
Else
MsgBox ("Fail")
End If
Else
MsgBox .Status & ": " & .StatusText
End If
End With
Parsing the JSON response:
The following reads in the results json from a file and parses out each resolve. It uses JSONConverter.bas. Note I have extracted the"results" JSON collection in my python script which would be the same as you doing json("results") on the converted JSON string via Set json = JsonConverter.ParseJson(.responseText)("results").
After adding JSONConverter.bas to your project you need to go tools > references > Add reference to Microsoft Scripting Runtime
Option Explicit
Public Sub GetJSONExtract()
Dim fso As Object, jsonFile As Object, jsonText As String, json As Object, item As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Set jsonFile = fso.OpenTextFile("C:\Users\User\Desktop\Sample.json")
jsonText = jsonFile.ReadAll
Set json = JsonConverter.ParseJson(jsonText) '<== Using results collection
'Set json = JsonConverter.ParseJson(.responseText)("results") '<== In your vba XMLHTTP version
For Each item In json
Debug.Print item("resolve")
Next
End Sub
As you were after how to parse the JSON that it was I have shown.
Additional notes:
I actually used the python script shown below; adapted from the API documentation. I then added in a bit of code to write the response out to a JSON file for later import. Run using Anaconda/Spyder.
import requests
import json
username = 'xxx'
key = 'yyy'
auth = (username, key)
base_url = 'https://api.passivetotal.org'
def passivetotal_get(path, query):
url = base_url + path
data = {'query': query}
response = requests.get(url, auth=auth, json=data)
return response.json()
pdns_results = passivetotal_get('/v2/dns/passive', 'passivetotal.org')
for resolve in pdns_results['results']:
print('Found resolution: {}'.format(resolve['resolve']))
with open(r"C:\Users\User\Desktop\Output.json", "w") as text_file:
text_file.write(json.dumps(pdns_results['results']))
That prints out all the resolves.
The original returned JSON structure looks like:
The object returned is a collection of dictionaries. You access the required value by the dictionary key "resolve"

How can you extract a nested JSON value?

Can someone with experience using JSON and Access together tell me what I'm doing wrong with this code? I'm trying to parse a JSON file and there's one nested data item that I can't seem to extract. The problem portion of the JSON data is as follows:
credits":{
"director":[{"displayName":"Bradley Cooper","firstName":"Bradley","lastName":"Cooper","bio":""}],
"cast":["Bradley Cooper"," Lady Gaga"," Andrew Dice Clay"," Dave Chappelle"," Sam Elliott"]
}
I can extract the cast names with no problem, but I can't retrieve the "displayname" for the director. The nested "{}" brackets inside the "director" item are throwing me off. Here's my code:
Sub JSON_prob_demo()
Dim url As String, data As String
Dim xml As Object, JSON As Object, colObj As Object, colobj2 As Object, colObj3 As Object, item As Object
Dim c1 As Variant, varX As Variant
url = "https://www.tiff.net/data/films/a-star-is-born.json"
Set xml = CreateObject("MSXML2.XMLHTTP")
With xml
.Open "GET", url, False
.send
data = .responseText
End With
Set JSON = JsonConverter.ParseJson(data)
Set colObj = JSON("credits")
For Each c1 In colObj("cast")
Debug.Print c1
Next
Debug.Print "Director:"
Set colobj2 = colObj("director")
For Each c1 In colobj2
Debug.Print c1("displayname")
Next
End Sub
I've been able to extract the names of the four director fields, but I simply cannot access their values. What's the trick?
Try this
Sub getHTTP()
Dim Url As String, data As String
Dim xml As Object, JSON As Object, colObj, item
Url = "https://www.tiff.net/data/films/a-star-is-born.json"
Set xml = CreateObject("MSXML2.ServerXMLHTTP")
With xml
.Open "GET", Url, False
.send
data = .responseText
End With
Set JSON = JsonConverter.ParseJson(data)
Set colObj = JSON("credits")("director")
For Each item In colObj
For j = 0 To item.Count - 1
Debug.Print item.Items()(j)
Next
Next
End Sub
Print
Note: Item is dictionary object so used Debug.Print item.Items()(j) to retrieve key values.

JSON to VBA - Error 13 mismatch on "root values"

I was trying to get some information from a JSON API and everything was going OK. So I started to get mismatch errors when I try to parse values that are inside the “root” of the JSON.
The code I use is below:
Public Sub Times()
Dim http As Object, JSON As Object, i As Integer
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", "https://api.cartolafc.globo.com/time/id/1084847/7", False
http.Send
Set JSON = ParseJson(http.responseText)
i = 2
Application.ScreenUpdating = False
Sheets("Youtube").Select
For Each Item In JSON
Sheets("Mais Escalados").Cells(i, 2).value = Item("pontos")
i = i + 1
Next
Application.ScreenUpdating = True
MsgBox ("Atualização Completa")
End Sub
I can parse the data inside atletas sub-items or any other header changing the code like this:
Sheets("Mais Escalados").Cells(i, 2).value = Item("atletas")("nome")
But when I try to parse information like pontos on the root I get the mismatch error.
This will give you the root value for the key "pontos":
JSON("pontos")
You can't loop over the root keys like you show in your posted code: you would need to check the type of each key's value before you try to write it to the sheet:
Public Sub Times()
Dim http As Object, JSON As Object, i As Integer, k
Set http = CreateObject("MSXML2.XMLHTTP")
http.Open "GET", "https://api.cartolafc.globo.com/time/id/1084847/7", False
http.Send
Set JSON = ParseJson(http.responseText)
For Each k In JSON
Debug.Print k, TypeName(JSON(k))
Next
End Sub
Output:
atletas Collection
clubes Dictionary
posicoes Dictionary
status Dictionary
capitao_id Double
time Dictionary
patrimonio Double
esquema_id Double
pontos Double
valor_time Double
rodada_atual Double

Issue with JsonConverter

this is the code I use to call parseJson in vba and in one case where I have a JSON object, I am receiving the error 10001 which relates to the latest Json-vba library 2.2.3 when the "{" or the "[" are expected.
Sub jsontest()
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
'http.Open "GET", "https://bin.codingislove.com/document/ayequrimiy", False
http.Open "GET", "https://bin.codingislove.com/ayequrimiy.json", False
http.send
MsgBox (ParseJson(http.responseText)("Count"))
End Sub
The second .json file shows the 10001 error but the first one, the same file in text form, is perfectly executing. I tried as well including brackets when I call the json string without success.
What should I correct in my parser call?
Using developer tools with call to your second url https://bin.codingislove.com/ukiyerovow.json, it can be seen that the json is returned from url https://bin.codingislove.com/documents/ukiyerovow like this:
{
"data":
"{
\"Count\":1,
\"results\":
[
{
\"showEmailIcon\":true,
\"showIcon\":true,
\"middleName\":\"\",
\"dateActivated\":1513000,
\"regAffiliateRebate\":\"No Rebate(0)\",
\"Id\":1,
\"dateLastLogin\":1513248842000,
\"countryName\":\"France\",
\"address\":null,
\"name\":\"cien\",
\"id\":1786511,
\"state\":null
}
],
\"resultClass\":\"com.zoho.dao.dto\"
}",
"key":"ayequrimiy"
}
Using Json-vba library this strign can be parsed like this. HTH
Sub jsontest()
Dim http As Object
Set http = CreateObject("MSXML2.XMLHTTP")
' use this url instaed:
Const url As String = "https://bin.codingislove.com/documents/ayequrimiy"
http.Open "GET", url, False
http.send
Dim parsedJson As Dictionary
Set parsedJson = JsonConverter.ParseJson(http.responseText)
Dim data As String
data = parsedJson("data")
Dim parsedData As Dictionary
Set parsedData = ParseJson(data)
MsgBox parsedData("Count")
End Sub
What should I correct in my parser call?
You have to correct the url. The second url should be https://bin.codingislove.com/documents/ayequrimiy. There is the json data.
Compare:
https://bin.codingislove.com/ayequrimiy.json
https://bin.codingislove.com/documents/ayequrimiy
To get e.g. Name you have to use the results which contains array so first point to the element of the array using index e.g. (1) and then take the element ("Name"):
Debug.Print parsedData("reports")(1)("Name")
Since this isn't a JSON response, you will have to make it one before you can a parse it as such. The easiest approach is to load the DOM of the page, and then extract the text.
There are lots of snippets on SO (here's one) that'll do just that.
Once you have the DOM, do something like this:
json = doc.getElementById("box").innerText

Loop through list of URLs using .Open (Parsing JSON in VBA)

The "reference URL list" part of the code is where I can drop an individual URL in and the code works fine. But I'd like to make the code more flexible where I can loop through my list of URLs (ideally only changing that portion of my code, or perhaps another small tweak). Here is the code:
Set MyRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
MyRequest.Open "GET", "reference URL list"
MyRequest.Send
Dim Json As Object
Set Json = JsonConverter.ParseJson(MyRequest.ResponseText)
I realize there are multiple ways to approach this -- though I can't find specific information that will slightly augment my approach. I really appreciate the help.
Kyle
This code should do what you want. The key idea is to use an Array to hold the list of website you want to send an HTTP request.
You don't have to use an array typed into VBA as I show below, you could also use a Range in Excel.
Here's the code:
Public Sub HTTP_Req()
Dim MyRequest As Object: Set MyRequest = CreateObject("WinHttp.WinHttpRequest.5.1")
'Add all Urls you want to send a HTTP request to, in an Array
Dim MyUrls: MyUrls = Array("www.google.com", "www.yahoo.com", "www.bing.com")
Dim i As Long
Dim Json As Object
For i = LBound(MyUrls) To UBound(MyUrls)
With MyRequest
.Open "GET", MyUrls(i)
.Send
Set Json = JsonConverter.ParseJson(.ResponseText)
'Do something with the JSON object here
End With
Next
End Sub