Using Access VBA to read JSON data (part two) - json

The following subroutine reads Json data from an internet site:
Sub DistanceRetrieve()
Dim url As String, Response As String
Dim PointCordinate As String
Dim WinHttpReq As Object, Json As Object, Paths As Object, Item As Object, NextItem As Object,
Points As Object
Set WinHttpReq = CreateObject("MSXML2.XMLHTTP.6.0")
With WinHttpReq
url = "https://distances.dataloy.com/route/route?point=" & Latitude1 & "," & Longitude1 &
"&point=" & Latitude2 & "," & Longitude2 & "&block_sc=" & BlockSC & "&block_nw=" & BlockNW
&"&block_ne=" & BlockNE
.Open "GET", url, False
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "X-API-Key", "DnqL8TJbh77wn0DphKhhI6GPOy3fnKDt2fDUMB8j"
.Send
If .Status = 200 Then
Response = .ResponseText
Set Json = JsonConverter.ParseJson(Response)
Set Paths = Json("paths")
For Each Item In Paths
Distance = Item("distance")
Set Points = Item("points")
For Each NextItem In Points
PointCoordinate = NextItem("coordinates")
If PointCoordinate = SuezCanalCoordinate Then
MsgBox ("Sues Canal is on route.")
End Sub
End If
Next
MsgBox ("Sues Canal is not on route.")
End Sub
Next
End If
End With
End Sub
N.B. Some variables are declared as public.
The "For Each NextItem In Points" statement gives "Object required" error message when the code is run.
Hereinafter is the first part of the Json data:
{ "info" : { "copyrights" : [ "Viku AS"], "took" : 1346 }, "paths" : [ { "distance" : 10289.034929805617, "bbox" : [ -10.026282, 1.124421, 113.648011, 60.180576], "points" : { "coordinates" :[[4.960373,60.180576],[4.962496,60.162612],[4.986241,60.143381],[4.944009,60.137372],[4.944009,60.137372],[4.10086,59.45105],[4.100343,59.002301],[1.42238,50.973884],[1.328024,50.868336],[1.308167,50.854352],[1.16305,50.752155],[1.065639,50.683556],[0.919434,50.580593],[0.602589,50.517847],[-0.070224,50.384606],[-0.376109,50.333315],[-1.053553,50.219721],[-1.730997,50.106127],[-2.408441,49.992533],[-2.88957,49.908579],[-3.209387,49.787494],[-3.806258,49.561516],[-4.403129,49.335538],[-5.0,49.109559],[-5.51017,48.916407],[-5.861724,48.642143],[-6.169181,48.246339],[-6.476639,47.850535],[-6.784096,47.454732],[-7.091553,47.058928],[-7.39901,46.663124],[-7.706467,46.26732],[-8.013925,45.871517],[-8.321382,45.475713],[-8.628839,45.079909],[-8.936296,44.684106],[-9.243753,44.288302],[-9.55121,43.892498],[-9.858668,43.496694],[-9.891568,43.45434],[-10.023932,43.260722],[-10.026282,42.88182],[-10.026186,42.46553],[-10.026082,42.015288],[-10.025979,41.565047],[-10.025875,41.114806],[-10.025771,40.664564],[-10.025667,40.214323],[-10.025564,39.764081],[-10.02546,39.31384],[-10.025356,38.863598],[-10.025356,38.69584],[-9.998078,38.603524],[-9.908628,38.296362],[-9.78081,37.857447],[-9.652993,37.418532],[-8.509321,36.452241],[-7.973851,36.329996],[-7.438382,36.20775],[-6.902912,36.085504],[-6.367442,35.963259],[-6.200541,35.925156],[-6.100124,35.925653],[-5.749613,35.937037],[-5.608748,35.93671],[-5.551674,35.951129],[-5.508509,35.962488],[-5.469502,35.972753],[-5.427,35.983562],[-5.072639,36.028626],[-4.523336,36.098481],[-3.974033,36.168336],[-3.42473,36.238191],[-2.875427,36.308045],[-2.326124,36.3779],[-2.268051,36.385285],[-2.188115,36.395701],[-2.188115,36.395701],[6.546112,37.329032],[10.409177,37.409177],[32.220277,31.534171],[32.351432,31.366935],[32.367263,31.321183],[32.326332,31.275712],[32.310771,31.256955],[32.306485,31.250784],[32.305175,31.245859],[32.304701,31.240173],[32.304078,31.22865],[32.30432,31.220009],[32.305401,31.193798],[32.30868,31.101482],[32.30884,31.096973],[32.313349,30.959484],[32.315801,30.881891],[32.316749,30.832008],[32.317177,30.818442],[32.317669,30.81143],[32.319202,30.805119],[32.326993,30.777533],[32.335298,30.748205],[32.338183,30.736395],[32.340812,30.72563],[32.342098,30.720024],[32.343728,30.712811],[32.344013,30.705045],[32.342476,30.69789],[32.339344,30.684428],[32.33286,30.65656],[32.326093,30.627388],[32.324259,30.620015],[32.322923,30.616423],[32.315078,30.602051],[32.311989,30.596237],[32.309886,30.591612],[32.308995,30.589652],[32.304839,30.580932],[32.303776,30.570936],[32.303946,30.565621],[32.305155,30.560251],[32.309042,30.549282],[32.312801,30.544911],[32.320118,30.535669],[32.328006,30.526559],[32.33423,30.518056],[32.336188,30.513515],[32.336788,30.512121],[32.33897,30.506124],[32.343769,30.484001],[32.345429,30.476297],[32.349985,30.452174],[32.357802,30.435216],[32.362452,30.41324],[32.365013,30.401903],[32.371307,30.364477],[32.372001,30.362383],[32.372923,30.360631],[32.37432,30.358856],[32.413348,30.314052],[32.442811,30.282656],[32.4683,30.273356],[32.487274,30.267524],[32.506479,30.261663],[32.528893,30.253179],[32.538655,30.242899],[32.55205,30.223001],[32.565398,30.200991],[32.567636,30.193881],[32.568511,30.186485],[32.568866,30.174391],[32.569061,30.165197],[32.569083,30.164319],[32.571572,30.065582],[32.573102,30.053687],[32.583382,29.999835],[32.585442,29.991212],[32.586504,29.984021],[32.587067,29.978722],[32.587502,29.972775],[32.586472,29.964744],[32.58411,29.957644],[32.580461,29.950635],[32.57586,29.943603],[32.571401,29.939105],[32.567231,29.935892],[32.56278,29.931928],[32.559968,29.92972],[32.553295,29.924821],[32.550781,29.920572],[32.546005,29.90946],[32.546005,29.903795],[32.554049,29.852356],[32.553995,29.847614],[32.534316,29.773183],[32.54236,29.63257],[32.544371,29.61334],[32.548393,29.592358],[32.599674,29.480381],[32.614756,29.454118],[32.630844,29.417339],[32.740534,29.181429],[32.820887,29.008613],[32.940536,28.751286],[33.033041,28.592489],[33.181886,28.374556],[33.31458,28.18027],[33.340722,28.15811],[33.36586,28.137718],[33.60102,27.943094],[33.655442,27.898053],[33.674193,27.88355],[33.703706,27.860724],[33.761019,27.809153],[33.815465,27.752737],[33.818462,27.749631],[33.837436,27.72997],[34.074325,27.532797],[34.102887,27.509024],[34.102887,27.509024],[42.75544,14.336583],[43.189582,13.344682],[43.189582,13.344682],[43.185256,13.313807],[43.188028,13.271231],[43.19972,13.234306],[43.340825,12.677617],[43.364873,12.629906],[43.380267,12.599365],[43.469014,12.507192],[43.691211,12.418707],[43.71623,12.408744],[44.142069,12.239163],[44.567907,12.069581],[44.993746,11.9],[45.080711,11.927235],[45.520055,12.064824],[45.9594,12.202414],[46.398745,12.340004],[46.838089,12.477593],[47.277434,12.615183],[47.716778,12.752772],[48.156123,12.890362],[48.595468,13.027952],[49.034812,13.165541],[49.474157,13.303131],[49.913501,13.440721],[50.352846,13.57831],[50.792191,13.7159],[51.231535,13.853489],[51.67088,13.991079],[52.110225,14.128669],[52.549569,14.266258],[52.988914,14.403848],[58.438634,15.679702],[58.438634,15.679702],[80.701832,5.701832],[93.468048,6.26668],[93.644801,6.27979],[94.095453,6.313216],[94.546105,6.346642],[94.996757,6.380069],[95.087901,6.386743],[95.538651,6.419749],[95.6777,6.381267],[96.113066,6.26078],[96.548433,6.140293],[96.983799,6.019807],[97.419165,5.89932],[97.854532,5.778833],[97.977656,5.62999],[98.265553,5.281954],[98.553451,4.933918],[98.841348,4.585882],[99.185654,4.304885],[99.535149,4.019653],[99.738417,3.85376],[99.932689,3.69521],[100.281985,3.410141],[100.63128,3.125072],[100.784531,3.0],[101.0,2.813011],[101.196657,2.711314],[101.448781,2.580933],[101.651348,2.420594],[102.003384,2.139419],[102.028193,2.119605],[102.250359,1.920972],[102.390329,1.853969],[102.79612,1.65972],[102.802769,1.656537],[102.950924,1.561332],[103.149876,1.425921],[103.343872,1.260979],[103.376343,1.233371],[103.465236,1.19105],[103.508891,1.172935],[103.575394,1.14584],[103.616935,1.133238],[103.678364,1.124421],[103.732268,1.127876],[103.788245,1.161041],[103.829739,1.182472],[103.862881,1.19766],[103.881411,1.205894],[103.931297,1.228061],[103.992803,1.250861],[104.055691,1.259842],[104.10683,1.26537],[104.121072,1.267352],[104.161425,1.27297],[104.208418,1.278497],[104.241756,1.284231],[104.272688,1.289551],[104.327282,1.297842],[104.449603,1.40631],[104.530497,1.551922],[104.749777,1.946632],[104.969057,2.341341],[105.078485,2.538313],[105.133782,2.987055],[105.151868,3.133821],[105.387157,3.519243],[105.509613,3.719836],[105.509613,3.719836],[109.625784,12.40235],[113.648011,21.648011]] ....
N.B. Unnecessary part at the data end is truncated.

Related

Excel VBA-JSON: How do loop through an array inside a JSON object?

Consider this JSON object:
{
"fileName": "Batch_01032023_SakerItemData.xlsx",
"fileLocation": "C:\\Temp",
"message": "There are 3 errors. Please correct and try again.",
"error": [
"{Item} failed validation:Item is required.:8",
"{Type} failed validation:Type is required.:8",
"{Class} failed validation:Class is required.:8"
]
}
I am using the JsonConverter from this repo https://github.com/VBA-tools/VBA-JSON
Consider this VBA code:
Dim jsonObject As Object, item As Object
Dim objHTTP As Object
Dim url As String
Dim result As String
Dim async As Boolean
Dim body As String
body = "{""fileLocation"":""{fileLocation}""}"
body = Replace(body, "{fileLocation}", Replace(fileLocation, "\", "\\"))
Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")
With objHTTP
.Open "POST", url, async
.SetRequestHeader "Content-Type", "application/json"
.SetRequestHeader "Accept", "application/json"
.SetRequestHeader "Authorization", "Basic " + _
Base64Encode(authUser + ":" + authPassword)
.Send body
.waitForResponse
result = .responseText
End With
Set jsonObject = ParseJson(result)
*** What is the syntax here to loop through error object? ****
For Each item In jsonObject("error")(1)
Next
this line Set jsonObject = ParseJson(result) does not throw an error and seems to work, yet when I get to the 'for each' loop, I get Error # 424 'Object Required'.
My question is this: How can I loop through the 'error' array in the 'jsonObject' so that I can display the validation errors to the user? The error array is dynamic.
The key error returns a Collection, so first assign it to a variable declared as Collection...
Dim col As VBA.Collection
Set col = jsonObject("error")
Then loop through each item in the collection...
Dim itm As Variant
For Each itm In col
Debug.Print itm
Next itm

create JSON using JSONCOVERTER

I am wondering to create a json from VBA Outlook to export email as ticket on Osticket System
Everithing working well except when there's multiple attachments
I need to have this syntax
{
"alert": "true",
"autorespond": "true",
"source": "API",
"name": "Angy User",
"email": "Angry#somewhere.com",
"subject": "Help",
"topicId": "1",
"message": "data:text/html,</body></html>Please Help</body></html>",
"attachments": [
{ "MyFile.png": "data:image/png;base64,JVBERi0........." },
{ "MyFile.png": "data:image/png;base64,JVBERi0........." },
]
}
But using my code i get this
{
"alert": "true",
"autorespond": "true",
"source": "API",
"name": "Angy User",
"email": "Angry#somewhere.com",
"subject": "Help",
"topicId": "1",
"message": "data:text/html,</body></html>Please Help</body></html>",
"attachments": [
{ "MyFile.png": "data:image/png;base64,JVBERi0.........",
"MyFile.png": "data:image/png;base64,JVBERi0........." },
]
}
I use this to create the json
Dim Body As New Dictionary
Body.Add "alert", "true"
Body.Add "autorespond", "true"
Body.Add "source", "API"
Body.Add "name", myMsg.SenderName
Body.Add "email", FromAddress
Body.Add "subject", myMsg.Subject
Body.Add "topicId", CStr(rubriq)
Body.Add "message", "data:text/html," & strData 'myMsg.HTMLBody
Body.Add "attachments", Array(Attm1) 'attachments
Dim json As String
json = JsonConverter.ConvertToJson(Body, Whitespace:=" ")
Where the Attm1 is a dictionary filled in FOR loop
Attm1.Add oFile.FileName, "data:" & _
oFile.PropertyAccessor.GetProperty(PR_ATTACH_CONTENT_TYPE) & _
";" & "base64," & n.nodeTypedValue
I used this function
https://github.com/VBA-tools/VBA-JSON
The loop code
Dim attachments As New Collection
If myMsg.attachments.Count > 0 Then
Set fs = CreateObject("Scripting.FileSystemObject")
Set nAtt = xmlTicket.createElement("attachments")
nodeTicket.appendChild nAtt
For i = 1 To myMsg.attachments.Count
Set oFile = myMsg.attachments.Item(i)
'I only add attachments up to a limit in size
If oFile.Size <= MAX_ATTACHMENT Then
sTmpFile = fs.GetTempName
oFile.SaveAsFile sTmpFile
'Attachment data is always base64-coded
n.dataType = "bin.base64"
'The ADODB.Stream tweak allows to read binary files
Set data = CreateObject("ADODB.Stream")
data.Type = 1 'Binary
data.Open
data.LoadFromFile sTmpFile
'MSXML will base64-code it for us
n.nodeTypedValue = data.Read
'Using the bin.base64 structure means adding namespace'd attributes.
'For some reason, osTicket will complain for each extra attribute, so
'we get to clean up
n.Attributes.removeNamedItem "dt:dt"
'For some reason, getting the content-type is very unclear in Outlook
Set a = xmlTicket.createAttribute("type")
a.Value = oFile.PropertyAccessor.GetProperty(PR_ATTACH_CONTENT_TYPE)
n.Attributes.setNamedItem a
Dim Attm1 As New Dictionary
Attm1.Add oFile.FileName, "data:" & oFile.PropertyAccessor.GetProperty(PR_ATTACH_CONTENT_TYPE) & ";" & "base64," & n.nodeTypedValue
Kill sTmpFile
End If
Next
End If
Try something like this. It's easier to manage if you split out the various jobs into separate methods.
Const MAX_ATTACHMENT As Long = 500000 'or whatever
Sub MainSub()
Dim Body As Object, dict As Object, i As Long, json As String
Dim myMsg As Outlook.MailItem
'...
'...
Body.Add "attachments", New Collection
If myMsg.attachments.Count > 0 Then
For i = 1 To myMsg.attachments.Count
Set dict = AttachmentDict(myMsg.attachments.Item(i))
If Not dict Is Nothing Then 'check conversion happened
Body("attachments").Add dict
End If
Next
End If
json = JsonConverter.ConvertToJson(Body, Whitespace:=" ")
'...
'...
End Sub
'create a dictionary from an attachment if it meets the size limit
Function AttachmentDict(att As Outlook.Attachment)
Dim dict As Object, fso As Object, sTmpFile As String
If att.Size < MAX_ATTACHMENT Then
Set dict = CreateObject("Scripting.Dictionary")
Set fso = CreateObject("Scripting.FileSystemObject")
sTmpFile = fso.GetSpecialFolder(2) & "\" & fso.GetTempName
att.SaveAsFile sTmpFile
dict.Add att.Filename, "data:" & _
att.PropertyAccessor.GetProperty(PR_ATTACH_CONTENT_TYPE) & _
";" & "base64," & FileToBase64(sTmpFile)
Set AttachmentDict = dict
End If
End Function
Function FileToBase64(FilePath As String) As String
Const adTypeBinary = 1 ' Binary file is encoded
Dim objXML, objDocElem, objStream
' Open data stream from file
Set objStream = CreateObject("ADODB.Stream")
objStream.Type = adTypeBinary
objStream.Open
objStream.LoadFromFile FilePath
Set objXML = CreateObject("MSXml2.DOMDocument")
Set objDocElem = objXML.createElement("Base64Data")
objDocElem.DataType = "bin.base64"
objDocElem.nodeTypedValue = objStream.Read()
FileToBase64 = objDocElem.Text
End Function

Bitly API call using VBA Excel Macro

I'm trying to make an Excel Macro to automatically shorten URLs in an Excel file.
().
I found existing code however this applies to an old version of the API:
Bitly has instructions on how to connect to the new API version, however these are not written in VBA:
The Bitly API instructions also contain instructions on how to convert a V3 API call to a V4 API call:
I tried to fix this. In Excel I get the error
'{"message":"FORBIDDEN"'
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim objHTTP As Object
Dim Json, URL, result, AccToken, LongURL As String
If Not Intersect(Target, Range("B6:B100")) Is Nothing Then
If Target.Count > 1 Then Exit Sub 'If users selects more than one cell, exit sub to prevent bugs
If Target.Value = Empty Then Exit Sub
AccToken = Sheet1.Range("C4").Value
If AccToken = "" Then
MsgBox "Please enter your Bitly Access Token to get started" & vbCrLf & "hoi"
Exit Sub
End If
LongURL = Target.Value
Set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
URL = "https://api-ssl.bitly.com/v4/shorten"
objHTTP.Open "POST", URL, LongURL, False
objHTTP.setRequestHeader "Authorization", "Bearer {" & AccToken & "}"
'objHTTP.setRequestHeader "Authorization", "Bearer {TOKEN}"
objHTTP.setRequestHeader "Content-type", "application/json"
objHTTP.send (Json)
result = objHTTP.responseText
Range("C" & Target.Row).Value = Left(result, Len(result) - 1)
Set objHTTP = Nothing
End If
End Sub
AccToken should be without brackets { } like: objHTTP.setRequestHeader "Authorization", "Bearer " & AccToken
You Dim Json but you set no value to this variable (it is empty) and so you send and empty request objHTTP.send (Json).
Your LongURL shoud not go into tho .Open but into your JSON so it needs to be objHTTP.Open "POST", URL, False and Json = "{""long_url"": ""https://dev.bitly.com"", ""domain"": ""bit.ly"", ""group_guid"": ""Ba1bc23dE4F""}"
It should look something like below:
If Not Intersect(Target, Me.Range("B6:B100")) Is Nothing Then
If Target.Count > 1 Then Exit Sub 'If users selects more than one cell, exit sub to prevent bugs
If Target.Value = vbNullString Then Exit Sub
Dim AccToken As String
AccToken = Sheet1.Range("C4").Value
If AccToken = vbNullString Then
MsgBox "Please enter your Bitly Access Token to get started" & vbCrLf & "hoi"
Exit Sub
End If
Dim LongURL As String
LongURL = Target.Value
Dim objHTTP As Object
Set objHTTP = CreateObject("MSXML2.ServerXMLHTTP")
Dim URL As String
URL = "https://api-ssl.bitly.com/v4/shorten"
objHTTP.Open "POST", URL, False
objHTTP.setRequestHeader "Authorization", "Bearer " & AccToken
objHTTP.setRequestHeader "Content-type", "application/json"
Dim Json As String
Json = "{""long_url"": """ & LongURL & """, ""domain"": ""bit.ly"", ""group_guid"": ""Ba1bc23dE4F""}"
objHTTP.send Json
Dim result As String
result = objHTTP.responseText
Me.Range("C" & Target.Row).Value = Left(result, Len(result) - 1)
Set objHTTP = Nothing
End If

VBA to parse data from web API

Public Sub IMPORTMESTER()
Dim xTOK As String
Dim URL As String
Dim httpREQ As Object
Dim JSON As Object
Dim xLINE As Variant
xTOK = "bdj62bzknriy3dd9g561on2xl2"
URL = "https://api.smartsheet.com/2.0/sheets/7352150637471620"
Set httpREQ = CreateObject("MSXML2.XMLHTTP.6.0")
With httpREQ
.Open "GET", URL, False
.setRequestHeader "Authorization", "Bearer " & xTOK
.setRequestHeader "Content-Type", "application/json"
.Send
End With
xLINE = httpREQ.ResponseText
MsgBox ("Complete!")
End Sub
So, Ive returned data I need, but I tried several methods to parse it and paste in excel, but without success. Here is the part of responsetext:
"cells":[{"columnId":2400415921792900,"value":"MWP08","displayValue":"MWP08"},{"columnId":6904015549163396,"value":"A-WP-80301D5D10C00","displayValue":"A-WP-80301D5D10C00"},{"columnId":1274516014950276,"value":"MWP0830W27V50KD","displayValue":"MWP0830W27V50KD"},{"columnId":5778115642320772,"value":"WP08 30W,120-277VAC,Ra70 5000K Clear lens,Dark bronze","displayValue":"WP08 30W,120-277VAC,Ra70 5000K Clear lens,Dark bronze"},{"columnId":3526315828635524,"value":"image002.png","displayValue":"image002.png","formula":"=SYS_CELLIMAGE(\"image002.png\",\"vDOY-InMRamvhitNGotKzb\",35,52,\"image.png\")","image":{"id":"vDOY-InMRamvhitNGotKzb","height":35,"width":52,"altText":"image002.png"}},{"columnId":8029915456006020},{"columnId":711566061528964,"value":1884.0,"displayValue":"1884","linkInFromCell":{"status":"INACCESSIBLE","sheetId":4533800614029188,"rowId":null,"columnId":null,"sheetName":"MLC-Inventory扣减(2019)"}},{"columnId":2963365875214212,"value":"https://mesterleds.com/wp-content/uploads/2017/12/WP01-45W70W.png","displayValue":"https://mesterleds.com/wp-content/uploads/2017/12/WP01-45W70W.png"},{"columnId":7466965502584708},{"columnId":1837465968371588},{"columnId":6341065595742084},{"columnId":4089265782056836},{"columnId":8592865409427332},{"columnId":430091084818308,"value":175.0,"displayValue":"175"},{"columnId":4933690712188804},{"columnId":2681890898503556},{"columnId":7185490525874052},{"columnId":1555990991660932},{"columnId":6059590619031428}]},{"id":7080298036914052,"rowNumber":3,"siblingId":2576698409543556,"expanded":true,"createdAt":"2019-01-31T00:06:35Z","modifiedAt":"2019-02-18T16:56:50Z",
Each row of table I need starts with:"cells';[{" while I only need "displayValue": for columns!
I tried several solutions and suggestions from various threads from StackOverflow but... no luck!
Below is desired output:
Final excel format (unneccessary columns hidden)
If only after displayValue you can use the following with jsonconverter.bas. You add the .bas to your project and then VBE > Tools > References> Add a reference to Microsoft Scripting Runtime.
Option Explicit
Public Sub IMPORTMESTER()
Dim xTOK As String
Dim URL As String
Dim httpREQ As Object
Dim json As Object
Dim xLINE As Variant
xTOK = "token"
URL = "https://api.smartsheet.com/2.0/sheets/7352150637471620"
Set httpREQ = CreateObject("MSXML2.XMLHTTP.6.0")
With httpREQ
.Open "GET", URL, False
.setRequestHeader "Authorization", "Bearer " & xTOK
.setRequestHeader "Content-Type", "application/json"
.send
End With
xLINE = httpREQ.responseText
Set json = JsonConverter.ParseJson(xLINE)("rows")
Dim item As Object, nextitem As Object, i As Long
For Each item In json
For Each nextitem In item("cells")
i = i + 1
ActiveSheet.Cells(i, 1) = nextitem("displayValue")
Next
Next
End Sub
The item you want is nested within the json where {} is a dictionary, and [] is a collection.

Extract data locations from map

I want to extract data from a map then get and store the locations all charging station in a specific state. (eg: https://www.plugshare.com/)
How can this be done? I don't mind using any programming language but which one is the best one for this application?
You can retrieve the data directly from https://www.plugshare.com with XHRs. You have to look into a little how does a website work to scrape the data. For any dynamically loaded data you just inspect XHRs the webpage does, find the one containing the relevant data, make the same XHR (either site provides API or not) and parse response. Navigate the page e. g. in Chrome, then open Developer Tools window (F12), Network tab, reload F5 the page and examine XHRs in the list.
There is one of the requests to URL https://www.plugshare.com/api/locations/region?... that returns latitude, longitude and other info for charging stations in a rectangle viewport area with specified coordinates. You can find URL, query parameters and some necessary headers as shown below:
Response is in JSON format:
You need to add basic authorization header to request. To retrieve the credentials go to Sources tab, add XHR Breakpoint for URL contains https://www.plugshare.com/api/locations/region, reload F5 the page, when the page is paused on XHR, follow the Call Stack frame by frame:
Skip any NREUM and nrWrapper objects that are the part of New Relic functionality. Click pretty-print {} to format source. Search e. g. Basic, Authorization or setRequestHeader in the sources, for that particular case first match is found in https://www.plugshare.com/js/main.js?_=1:
Click a station on the map and you get one more XHR appeared with URL like https://www.plugshare.com/api/locations/[id] with detailed information for that station, as shown below:
Response is in JSON format also:
Also you may get data for stations from URL like https://www.plugshare.com/api/stations/[id].
You may use the below VBA code to retrieve info as described above. Import JSON.bas module into the VBA project for JSON processing.
Option Explicit
Sub Test_www_plugshare_com()
Const Transposed = False ' Output option
Const Detailed = True ' Scrape option
Dim sResponse As String
Dim aQryHds()
Dim oQuery As Object
Dim sQuery As String
Dim vRegionJSON
Dim sState As String
Dim aResult()
Dim i As Long
Dim vLocationJSON
Dim aRows()
Dim aHeader()
' Retrieve auth token
XmlHttpRequest "GET", "https://www.plugshare.com/js/main.js?_=1", "", "", "", sResponse
With RegExMatches(sResponse, "var s\=""(Basic [^""]*)"";") ' var s="Basic *";
If .Count > 0 Then
aQryHds = Array( _
Array("Authorization", .Item(0).SubMatches(0)), _
Array("Accept", "application/json") _
)
Else
MsgBox "Can't retrieve auth token"
Exit Sub
End If
End With
' Set query parameters
Set oQuery = CreateObject("Scripting.Dictionary")
With oQuery
.Add "minimal", "1"
.Add "count", "500"
.Add "latitude", "19.697593650121235"
.Add "longitude", "-155.06529816792295"
.Add "spanLng", "0.274658203125"
.Add "spanLat", "0.11878815323507652"
.Add "access", "1,3"
.Add "outlets", "[{""connector"":1},{""connector"":2},{""connector"":3},{""connector"":4},{""connector"":5},{""connector"":6,""power"":0},{""connector"":6,""power"":1},{""connector"":7},{""connector"":8},{""connector"":9},{""connector"":10},{""connector"":11},{""connector"":12},{""connector"":13},{""connector"":14},{""connector"":15}]"
.Add "fast", "add"
End With
sQuery = EncodeQueryParams(oQuery)
' Retrieve a list of stations for the viewport
XmlHttpRequest "GET", "https://www.plugshare.com/api/locations/region?" & sQuery, aQryHds, "", "", sResponse
' Parse JSON response
JSON.Parse sResponse, vRegionJSON, sState
If sState <> "Array" Then
MsgBox "Invalid JSON response"
Exit Sub
End If
' Populate result array
ReDim aResult(UBound(vRegionJSON))
' Extract selected properties from parsed JSON
For i = 0 To UBound(aResult)
Set aResult(i) = ExtractKeys(vRegionJSON(i), Array("id", "name", "latitude", "longitude"))
DoEvents
Next
If Detailed Then
' Populate result array with detailed info for each location
For i = 0 To UBound(aResult)
' Retrieve detailed info for each location
XmlHttpRequest "GET", "https://www.plugshare.com/api/locations/" & aResult(i)("id"), aQryHds, "", "", sResponse
' Parse JSON response
JSON.Parse sResponse, vLocationJSON, sState
If sState = "Object" Then
' Extract selected properties from parsed JSON
Set aResult(i) = ExtractKeys(vLocationJSON, Array("reverse_geocoded_address", "hours", "phone", "description"), aResult(i))
End If
DoEvents
Next
End If
' Convert resulting array to arrays for output
JSON.ToArray aResult, aRows, aHeader
' Output
With ThisWorkbook.Sheets(1)
.Cells.Delete
If Transposed Then
Output2DArray .Cells(1, 1), WorksheetFunction.Transpose(aHeader)
Output2DArray .Cells(1, 2), WorksheetFunction.Transpose(aRows)
Else
OutputArray .Cells(1, 1), aHeader
Output2DArray .Cells(2, 1), aRows
End If
.Columns.AutoFit
End With
MsgBox "Completed"
End Sub
Sub XmlHttpRequest(sMethod As String, sUrl As String, arrSetHeaders, sFormData, sRespHeaders As String, sContent As String)
Dim arrHeader
'With CreateObject("Msxml2.ServerXMLHTTP")
' .SetOption 2, 13056 ' SXH_SERVER_CERT_IGNORE_ALL_SERVER_ERRORS
With CreateObject("MSXML2.XMLHTTP")
.Open sMethod, sUrl, False
If IsArray(arrSetHeaders) Then
For Each arrHeader In arrSetHeaders
.SetRequestHeader arrHeader(0), arrHeader(1)
Next
End If
.send sFormData
sRespHeaders = .GetAllResponseHeaders
sContent = .responseText
End With
End Sub
Function RegExMatches(sText, sPattern, Optional bGlobal = True, Optional bMultiLine = True, Optional bIgnoreCase = True) As Object
With CreateObject("VBScript.RegExp")
.Global = bGlobal
.MultiLine = bMultiLine
.IgnoreCase = bIgnoreCase
.Pattern = sPattern
Set RegExMatches = .Execute(sText)
End With
End Function
Function EncodeQueryParams(oParams As Object) As String
Dim aParams
Dim i As Long
aParams = oParams.Keys()
For i = 0 To UBound(aParams)
aParams(i) = EncodeUriComponent((aParams(i))) & "=" & EncodeUriComponent((oParams(aParams(i))))
Next
EncodeQueryParams = Join(aParams, "&")
End Function
Function EncodeUriComponent(strText As String) As String
Static objHtmlfile As Object
If objHtmlfile Is Nothing Then
Set objHtmlfile = CreateObject("htmlfile")
objHtmlfile.parentWindow.execScript "function encode(s) {return encodeURIComponent(s)}", "jscript"
End If
EncodeUriComponent = objHtmlfile.parentWindow.encode(strText)
End Function
Function ExtractKeys(oSource, aKeys, Optional oTarget = Nothing) As Object
Dim vKey
If oTarget Is Nothing Then Set oTarget = CreateObject("Scripting.Dictionary")
For Each vKey In aKeys
If oSource.Exists(vKey) Then
If IsObject(oSource(vKey)) Then
Set oTarget(vKey) = oSource(vKey)
Else
oTarget(vKey) = oSource(vKey)
End If
End If
Next
Set ExtractKeys = oTarget
End Function
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
Change to Const Detailed = False if you have a lot of items for output to prevent application hanging, since XHRs are in synchronous mode. The output for me with specified viewport coordinates is as follows:
BTW, the similar approach applied in other answers.