I have a lotus agent running with lotusscript. Form the browser I post form data to the webserver and I receive this data with the following lotusscript:request_method = doc.GetItemValue( "request_content" )(0)
But if I have a form with for example name and phonenumber. Then my agent receives this as name=bla&phonenumber=243525
How can i separate these fields actually and secondly how can I receive XML on this agent so that I can extract this and put in to a document. I googled a lot but still got no solutions.
The way you get the data differs if the client makes a GET or a POST.
If this is a get, all the parameters are in the url in a url format.
Many many ressource on the web will give you some code to parse this url and get name and value a simple search in goolge will bring : http://searchdomino.techtarget.com/tip/Parsing-URL-Parameters-with-Ease
I use generally the following code, which add in the document context the fields received on url or on post.
Dim s As NotesSession
Set s = New notessession
Set doc = s.documentcontext
Dim myQuerystring As String
If doc Is Nothing Then
logErrorEX "getting a call without document context ?!?","", doc,""
GoTo returnErr
End If
If doc.QUERY_STRING_DECODED(0)<>"" Then'it's a GET
myQuerystring = doc.QUERY_STRING_DECODED(0)
ElseIf doc.QUERY_STRING(0)<>"" Then
myQuerystring = doc.QUERY_STRING(0)
'decode it !
ElseIf doc.REQUEST_CONTENT(0)<>"" Then'it's a POST
myQuerystring = doc.REQUEST_CONTENT(0) ' WARNING this is for POST but you will have to decode !!!
'decode it !
Else
logErrorEX "getting a call with document context but without query_string?!?","", doc,""
GoTo returnErr
End if
Call ExplodeQueryString(myQuerystring, doc)
Private Sub ExplodeQueryString (QueryString As String,doc As NotesDocument )
Dim ArgsList As Variant
ArgsList = Split (QueryString, "&")
If IsArray(ArgsList) Then
debugString = debugString+"ArgsList is an array of " & UBound(ArgsList)
Else
debugString = debugString+"ArgsList is NOT an array ??? " & ArgsList
End if
Dim ArgKey As String
Dim ArgValue As String
ForAll Arg In ArgsList
If left$(Arg, 1)= "_" Or Left$(Arg, 1)= "%" Then
'ignore it
else
ArgKey = strleft(Arg, "=")
If ArgKey = "" Then
'ignore it?
else
ArgValue = strright$(Arg, "=")
' AgentArgs(ArgKey) = ArgValue
doc.Replaceitemvalue ArgKey, ArgValue
End If
End if
End ForAll
End Sub
I didn't declare some global variable like debugString to shorten in.
The format you are seeing is the convention used by all web browser software to encode field data from forms. You can use functions similar to the ExplodeQueryString function in the code posted by Emmanual to parse it. It looks to me like he is taking each "&name" portion and creating a NotesItem with that name and using it to store the value from the "=value" portion. You can do that, or you can use a List, or whatever best fits your requirements.
There is no rule against sending POST data in other formats without using the &name=value convention. It just requires agreement between whatever software is doing the sending and your software on the receiving side. If they want to send you XML in the POST data, that's fine. You can use standard XML parsing functions to deal with it. Notes comes with a NotesDOMParsesr class that you can use if you want. If you are running on Windows, you can use Microsoft.XMLDOM instead.
I wrote a class a while back that does exactly what you ask for. It splits the query string (or request content) into a list of values, with the name as the list tag.
http://blog.texasswede.com/free-code-class-to-read-url-name-value-pairs/
Here is the code (I usually put it in a script library called Class.URL):
%REM
Library Class.URL
Created Oct 9, 2014 by Karl-Henry Martinsson
Description: Lotusscript class to handle incoming URL (GET/POST).
%END REM
Option Public
Option Declare
%REM
Class URLData
Description: Class to handle URL data passed to web agent
%END REM
Class URLData
p_urldata List As String
%REM
Sub New()
Description: Create new instance of URL object from NotesDocument
%END REM
Public Sub New()
Dim session As New NotesSession
Dim webform As NotesDocument
Dim tmp As String
Dim tmparr As Variant
Dim tmparg As Variant
Dim i As Integer
'*** Get document context (in-memory NotesDocument)
Set webform = session.DocumentContext
'*** Get HTTP GET argument(s) after ?OpenAgent
tmp = FullTrim(StrRight(webform.GetItemValue("Query_String")(0),"&"))
If tmp = "" Then
'*** Get HTTP POST argument(s) after ?OpenAgent
tmp = FullTrim(StrRight(webform.GetItemValue("Request_Content")(0),"&"))
End If
'*** Separate name-value pairs from each other into array
tmparr = Split(tmp,"&")
'*** Loop through array, split each name-value/argument
For i = LBound(tmparr) To UBound(tmparr)
tmparg = Split(tmparr(i),"=")
p_urldata(LCase(tmparg(0))) = Decode(tmparg(1))
Next
End Sub
%REM
Function GetValue
Description: Get value for specified argument.
Returns a string containing the value.
%END REM
Public Function GetValue(argname As String) As String
If IsElement(p_urldata(LCase(argname))) Then
GetValue = p_urldata(LCase(argname))
Else
GetValue = ""
End If
End Function
%REM
Function IsValue
Description: Check if specified argument was passed in URL or not.
Returns boolean value (True or False).
%END REM
Public Function IsValue(argname As String) As Boolean
If IsElement(p_urldata(LCase(argname))) Then
IsValue = True
Else
IsValue = False
End If
End Function
'*** Private function for this class
'*** There is no good/complete URL decode function in Lotusscript
Private Function Decode(txt As String) As String
Dim tmp As Variant
Dim tmptxt As String
tmptxt = Replace(txt,"+"," ")
tmp = Evaluate(|#URLDecode("Domino";"| & tmptxt & |")|)
Decode = tmp(0)
End Function
End Class
And this is how you can use it:
Option Public
Option Declare
Use "Class.URL"
Sub Initialize
Dim url As URLData
'*** Create new URLData object
Set url = New URLData()
'*** MIME Header to tell browser what kind of data we will return
Print "content-type: text/html"
'*** Check reqired values for this agent
If url.IsValue("name")=False Then
Print "Missing argument 'name'."
Exit Sub
End If
'*** Process name argument
If url.GetValue("name")="" Then
Print "'Name' is empty."
Else
Print "Hello, " + url.GetValue("name") + "!"
End If
End Sub
Related
I have been supplied a certificate that is required to send a json claim to an uri that has an api for sending the claim. The line in error is: handler.ClientCertificates.Add(certificate)
The following is my code:
Public Shared Sub Main()
Dim path As String = "E:\DRDRW_Update\Web Based Billing\vendorsupplied.pfx"
Dim password As String = "Password"
Dim strGateway As String = "https://MCE/api/WebServiceClaim"
Dim collection = New X509Certificate2Collection()
collection.Import(path, password, X509KeyStorageFlags.Exportable)
Dim certificate = collection(0)
Dim PathClaim As String = "E:\Sample Claim Submission JSON.txt"
Dim fi As New IO.FileInfo(PathClaim)
Dim jsonclaim As String = IO.File.ReadAllText(fi.FullName)
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
Dim handler As New WebRequestHandler()
handler.ClientCertificates.Add(certificate)
' custom certificate validation handler to ignore untrusted remote certificate
ServicePointManager.ServerCertificateValidationCallback = New RemoteCertificateValidationCallback(AddressOf ValidateServerCertificate)
Using client = New HttpClient(handler)
Dim serializedProduct = JsonConvert.SerializeObject(jsonclaim)
Dim content = New StringContent(serializedProduct, Encoding.UTF8, "application/json")
content.Headers.Add("header1", "header2") ' require header
content.Headers.Add("token", "xxxxxxx-yyyy-zzzz")
Dim result = client.PostAsync(strGateway, content).Result ' ensures task is synchronous
' deserialize the saveresultmodel from the WS response and check for claim validation errors
Dim success As Boolean = False
If result.IsSuccessStatusCode Then
Dim resultResult As String = result.Content.ReadAsStringAsync().Result
Dim claimResult = JsonConvert.DeserializeObject(resultResult)
If claimResult.Errors.Count = 0 Then
success = True
Else
' output error results to console
For Each [error] In claimResult.Errors
Console.WriteLine(JsonConvert.SerializeObject([error]))
Next [error]
End If
End If
End Using
End Sub
Public Class WebRequestHandler
Inherits HttpClientHandler
End Class
The issue is that you have declared your own class for the WebRequestHandler. This is not the same as the System.Net.Http.WebRequestHandler.
Visual Studio had a "nice" feature offering to do this for you when it discovers a missing reference with something like "Generate Class for WebRequestHandler." This usually happens when you copy/paste code from another source and the reference was not fully defined. I never take this option and find it a weird way to approach writing code.
You do need to change your code to reference the correct handler like so.
Dim handler As New System.Net.Http.WebRequestHandler
The reason you are getting an error with that is that your project now needs a reference to another assembly (dll) with that class in it. To figure this out, I used google to find the documentation page here and noted this text: Assembly:
System.Net.Http.WebRequest (in System.Net.Http.WebRequest.dll).
This tells us to add the System.Net.Http.WebRequest reference to the project in order to access this class.
I had two issues, the first was answered by #GMan80013 by putting a reference to System.Net.Http.WebRequest.dll. The other was caused by the use of self-signing certificates. The ServicePointManager.ServerCertificateValidationCallback function needs to return true to ignore the issue of certificate validation caused by self-signed certificates.
This is a quick and dirty POC I have so far from other helpful Stack posts:
Public Function WebRequest(url As String) As String
Dim http As MSXML2.xmlhttp
Set http = CreateObject("MSXML2.ServerXMLHTTP")
http.open "GET", url, False
http.send
WebRequest = http.responseText
Set http = Nothing
End Function
Private Sub Command1_Click()
Dim http As MSXML2.xmlhttp
Dim result As String
Dim url As String
Dim productId As String
productId = "2"
url = "http://localhost:1111/api/products/" & productId
result = WebRequest(url)
MsgBox result
End Sub
This calls a simple web API and returns as expected. The response reads as:
{"Id":2,"Name":"Yo-yo","Category":"Toys","Price":3.75}
What is the best way to assign the parameters to variables for use within the rest of the app?
There is no "best" way to parse JSON, but there are several existing VB6 classes for doing so. There is nothing built into VB6 or in Windows you can use though, so there isn't any obvious choice to reach for first.
If you don't want to use an existing VB6 class or a 3rd party library then you could just "manually" do the parsing with your own code. As long as the JSON you expect is pretty simple that might be all you need.
Many pitfalls here but it works for your very simple case as long as no other data types are used, the strings never have quotes or escaped symbols, etc.:
Option Explicit
Private Sub Main()
Const SIMPLE_JSON As String = _
"{""Id"":2,""Name"":""Yo-yo"",""Category"":""Toys"",""Price"":3.75}"
Dim JsonItems() As String
Dim Collection As Collection
Dim I As Long
Dim Parts() As String
Dim Value As Variant
JsonItems = Split(Mid$(SIMPLE_JSON, 2, Len(SIMPLE_JSON) - 2), ",")
Set Collection = New Collection
For I = 0 To UBound(JsonItems)
Parts = Split(JsonItems(I), ":")
Parts(0) = Mid$(Parts(0), 2, Len(Parts(0)) - 2)
If Left$(Parts(1), 1) = """" Then
Value = Mid$(Parts(1), 2, Len(Parts(1)) - 2)
Else
Value = Val(Parts(1))
End If
Collection.Add Array(Parts(0), Value), Parts(0)
Next
With Collection
For I = 1 To .Count
Debug.Print .Item(I)(0); "="; .Item(I)(1)
Next
End With
End Sub
Result:
Id= 2
Name=Yo-yo
Category=Toys
Price= 3.75
The Val() function is used for the non-String values because it is locale blind (always uses the invariant locale, which JSON numbers should always be formatted for).
I have Web API controller that retrieves ticket information. At the start -- The API is called and the request is routed to the proper controller function. The controller passes a request to the database. From there the retrieved data is ran through a dictionary block where the field name is associated with the data. Next the data is serialized. Then the data is passed back to the controller. At this point I know the Json string looks good. But, when the properly formatted json data is passed back to the caller a bunch slashes are added to the output.
My understanding is that Web API is supposed to automatically format the return data. I suspect I am formatting the data for the controller correctly before it is returned.
Public Function GetTicketSearch(ByVal SourceTktNum As String) As Object
'GET api/outage/SourceTktNum
Dim strFullName As String = MethodBase.GetCurrentMethod().ReflectedType.FullName
Dim strMethodName As String = MethodBase.GetCurrentMethod().Name
Dim strClassRoutine As String = strMethodName & " / " & strFullName
Try
Dim objJsonRptRtn As Object = GetReportData_bllBLL.BLL__DataSet__GetReportData__GetData(strMARCLSysId, strLogonSysId, SourceTktNum)
'AT THIS POINT I KNOW THE JSON STRING LOOKS AS IT SHOULD.
Return objJsonRptRtn
'AFTER THE ABOVE STATEMENT SOMETHING HAPPENS TO THE DATA / SLASHES ARE ADDED TO THE OUTPUT TO BE RETURNED BY THE API
Catch ex As Exception
Dim strExMessage As String = ex.Message
Dim strStackTrace As String = ex.StackTrace
Dim strMsg As String = strExMessage & ControlChars.CrLf & ControlChars.Lf & strStackTrace & ControlChars.CrLf & ControlChars.Lf
MailLogEvent.BLL__Process__MailAndLogEvent__AddLogEntry(strMARCLSysId, strLogonSysId, 901020, dteTime_Start, 0, strMsg, strClassRoutine)
Throw New HttpResponseException(HttpStatusCode.InternalServerError)
End Try
End Function
Code to create JSON object to be passed back to the controller...
'--------------------------------------------------------- Create Json String
Dim dctDataDictionary As New Dictionary(Of String, String)
dctDataDictionary.Add("sourceTktNum", strSourceTktNumKey)
dctDataDictionary.Add("incidentTime", strIncidentTime)
dctDataDictionary.Add("incidentEndTime", strIncidentEndTime)
dctDataDictionary.Add("recordTimeStamp", strRecordTimeStamp)
dctDataDictionary.Add("outageReasonCd", strOutageReasonCd)
dctDataDictionary.Add("numDS3", strNumDS3)
dctDataDictionary.Add("numBlocked", strNumBlocked)
dctDataDictionary.Add("numVOIP", strNumVOIP)
dctDataDictionary.Add("numWireline", strNumWireline)
dctDataDictionary.Add("numEndUserCircuits", strNumEndUserCircuits)
dctDataDictionary.Add("stateCd", strStateCd)
dctDataDictionary.Add("city", strCity)
dctDataDictionary.Add("incidentDescription", strIncidentDescription)
dctDataDictionary.Add("causeDesc", strCauseDesc)
dctDataDictionary.Add("equipFailedDesc", strEquipFailedDesc)
dctDataDictionary.Add("networkPartDesc", strNetworkPartDesc)
dctDataDictionary.Add("restoreMethodDesc", strRestoreMethodDesc)
objJsonRptRtn = New System.Web.Script.Serialization.JavaScriptSerializer().Serialize(dctDataDictionary)
Return objJsonRptRtn
This could be happening because you are again trying to convert a json data into json, which results in extra slashes.
Can you show the code before you return 'objJsonRptRtn'.
As it turned out I was double serializing. I removed the statement that serialized the dictionary output. Then, simply passed the dictionary back to the controller then let the controller return the dictionary. Everything works great...
'--------------------------------------------------------- Create Dictionary
dctDataDictionary.Add("sourceTktNum", strSourceTktNumKey)
dctDataDictionary.Add("incidentTime", strIncidentTime)
dctDataDictionary.Add("incidentEndTime", strIncidentEndTime)
dctDataDictionary.Add("recordTimeStamp", strRecordTimeStamp)
dctDataDictionary.Add("outageReasonCd", strOutageReasonCd)
dctDataDictionary.Add("numDS3", strNumDS3)
dctDataDictionary.Add("numBlocked", strNumBlocked)
dctDataDictionary.Add("numVOIP", strNumVOIP)
dctDataDictionary.Add("numWireline", strNumWireline)
dctDataDictionary.Add("numEndUserCircuits", strNumEndUserCircuits)
dctDataDictionary.Add("stateCd", strStateCd)
dctDataDictionary.Add("city", strCity)
dctDataDictionary.Add("incidentDescription", strIncidentDescription)
dctDataDictionary.Add("causeDesc", strCauseDesc)
dctDataDictionary.Add("equipFailedDesc", strEquipFailedDesc)
dctDataDictionary.Add("networkPartDesc", strNetworkPartDesc)
dctDataDictionary.Add("restoreMethodDesc", strRestoreMethodDesc)
Return dctDataDictionary
Public Function GetTicketSearch(ByVal SourceTktNum As String) As Object
'GET api/outage/SourceTktNum
Dim strFullName As String = MethodBase.GetCurrentMethod().ReflectedType.FullName
Dim strMethodName As String = MethodBase.GetCurrentMethod().Name
Dim strClassRoutine As String = strMethodName & " / " & strFullName
Try
Dim dctDataDictionary As Object = GetReportData_bllBLL.BLL__DataSet__GetReportData__GetData(strMARCLSysId, strLogonSysId, SourceTktNum)
If dctDataDictionary Is Nothing Then
Throw New HttpResponseException(HttpStatusCode.PartialContent)
Else
Return dctDataDictionary
End If
Catch ex As Exception
Dim strExMessage As String = ex.Message
Dim strStackTrace As String = ex.StackTrace
Dim strMsg As String = strExMessage & ControlChars.CrLf & ControlChars.Lf & strStackTrace & ControlChars.CrLf & ControlChars.Lf
MailLogEvent.BLL__Process__MailAndLogEvent__AddLogEntry(strMARCLSysId, strLogonSysId, 901020, dteTime_Start, 0, strMsg, strClassRoutine)
Throw New HttpResponseException(HttpStatusCode.InternalServerError)
End Try
End Function
I'm using the newtonsoft.dll to deal qwith the proper JSON responses from a site, i have come in to an issue, the delete code:
Dim delPro As String
Dim resPro As String
Dim sB As New StringBuilder()
For Each row As DataGridViewRow In dataGridProjects.Rows
If (row.Cells(4).Value IsNot Nothing) Then
' JSON
delPro = srFunctions.postURL("http://www.ste.com/ajax_task.php?act=add&task=projectDelete", "project_ids=" + row.Cells(0).Value.ToString(), varCookieJar)
resPro = srFunctions.postURL("http://www.ste.com/ajax_task.php?act=status&task=projectDelete", "", varCookieJar)
' purely for debugging
sB.Append("1: " + delPro)
sB.Append(Environment.NewLine + "----------------------------------------------------------------" + Environment.NewLine)
sB.Append("2: " + resPro)
sB.Append(Environment.NewLine + "----------------------------------------------------------------" + Environment.NewLine)
' responses
Dim tempPost = New With {Key .message = "", Key .error = 0, Key .done = False, Key .jsdata = ""}
Dim obj = JsonConvert.DeserializeAnonymousType(resPro, tempPost)
Dim com As String = obj.message
Dim obj2 = JsonConvert.DeserializeObject(Of saperJsonObject)(resPro)
If CBool((CStr(obj2.done))) Then
dataGridProjects.Rows.Remove(row)
Me.returnMessage("Project has been deleted!")
Else
dataGridProjects.Rows.Remove(row)
Me.returnMessage("Site returned an unknown response! (The action still most likely was executed)" & vbCrLf & vbCrLf & "Returned response was: " & (CStr(obj2.done)))
End If
End If
Next
The site returns 2 different success responses, this one:
{"error":0,"done":0,"message":"\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0437\u0430\u0434\u0430\u043d\u0438\u0435: 0/1","data":[true,true,0,1]}
Has true,true,0,1 at the end which is a success (the action is still completed) the other one looks like:
{"error":0,"done":1}
or similar, the done:1 also denotes a success, i'm not sure how to look for both success values, i know i need to edit here : If CBool((CStr(obj2.done))) Then but i'm not sure how to go about it.
any help would be great guys!
cheers
Graham
Without a class structure, DeserializeObject is problematic (resPro at least is defined as String). It works, and you can get the done property, but requires Option Strict Off, which is not usually a good idea.
You can also just parse the response if that status is all you need to know.
Public Class Russianobject
<JsonProperty("error")>
Public Property JError As Integer
Public Property done As Boolean
Public Property message As String
Public Property data As Object() ' object array
End Class
This is what the longer message looks like (you didnt post what your saperJsonObject looked like) . I had to change the Error property because it is a reserved word in VB. Also, I changed done from Int32 to Boolean. The last item, data is just an array of objects, and it is not clear which element you need.
Even though the short response does not have all these elements, you can use the same class, message will be empty and datawill be Nothing, so you will have to check!
Dim jstr = from whereever russian objects come from
Dim jobj = JsonConvert.DeserializeObject(Of Russianobject)(jstr)
If jobj.data IsNot Nothing Then
Console.WriteLine("0: {0}, 1: {1}, 2:{2}, 3: {2}", jobj.data(0),
jobj.data(1), jobj.data(2), jobj.data(3))
Else
Console.WriteLine(jobj.done)
End If
This should work whether you get a long or short response. To simply parse it, you do not need a class:
' using the short one:
jstr =...from whereever
jp = JObject.Parse(jstr)
Dim jd = jp.SelectToken("data")
If jd IsNot Nothing Then
Console.WriteLine("0: {0}, 1: {1}, 2:{2}, 3: {2}", jd(0), jd(1), jd(2), jd(3))
Else
Console.WriteLine("done = " & Convert.ToBoolean(jp("done")))
End If
Note that in this case, the property name is used like a key.
Output:
0: True, 1: True, 2:0, 3: 0
done = 1
The long response results in the first, the short results in the second. Whether you use a class and deserialize to an object or simply parse it, you will have to check the data element for Nothing (as shown) since it wont exist in the short response.
Per example below...Looping through an object from a parsed JSON string returns an error "Object doesn't support this property or method". Could anyone advise how to make this work? Much appreciated (I spent 6 hours looking for an answer before asking here).
Function to parse JSON string into object (this works OK).
Function jsonDecode(jsonString As Variant)
Set sc = CreateObject("ScriptControl"): sc.Language = "JScript"
Set jsonDecode = sc.Eval("(" + jsonString + ")")
End Function
Looping through the parsed object returns error "Object doesn't support this property or method".
Sub TestJsonParsing()
Dim arr As Object 'Parse the json array into here
Dim jsonString As String
'This works fine
jsonString = "{'key1':'value1','key2':'value2'}"
Set arr = jsonDecode(jsonString)
MsgBox arr.key1 'Works (as long as I know the key name)
'But this loop doesn't work - what am I doing wrong?
For Each keyName In arr.keys 'Excel errors out here "Object doesn't support this property or method"
MsgBox "keyName=" & keyName
MsgBox "keyValue=" & arr(keyName)
Next
End Sub
PS. I looked into these libraries already:
-vba-json Wasn't able to get the example working.
-VBJSON There's no vba script included (this might work but don't know how to load it into Excel and there is minimum documentation).
Also, Is it possible to access Multidimensional parsed JSON arrays? Just getting a basic key/value array loop working would be great (sorry if asking too much). Thanks.
Edit: Here are two working examples using the vba-json library. The question above is still a mystery though...
Sub TestJsonDecode() 'This works, uses vba-json library
Dim lib As New JSONLib 'Instantiate JSON class object
Dim jsonParsedObj As Object 'Not needed
jsonString = "{'key1':'val1','key2':'val2'}"
Set jsonParsedObj = lib.parse(CStr(jsonString))
For Each keyName In jsonParsedObj.keys
MsgBox "Keyname=" & keyName & "//Value=" & jsonParsedObj(keyName)
Next
Set jsonParsedObj = Nothing
Set lib = Nothing
End Sub
Sub TestJsonEncode() 'This works, uses vba-json library
Dim lib As New JSONLib 'Instantiate JSON class object
Set arr = CreateObject("Scripting.Dictionary")
arr("key1") = "val1"
arr("key2") = "val2"
MsgBox lib.toString(arr)
End Sub
The JScriptTypeInfo object is a bit unfortunate: it contains all the relevant information (as you can see in the Watch window) but it seems impossible to get at it with VBA.
If the JScriptTypeInfo instance refers to a Javascript object, For Each ... Next won't work. However, it does work if it refers to a Javascript array (see GetKeys function below).
So the workaround is to again use the Javascript engine to get at the information we cannot with VBA. First of all, there is a function to get the keys of a Javascript object.
Once you know the keys, the next problem is to access the properties. VBA won't help either if the name of the key is only known at run-time. So there are two methods to access a property of the object, one for values and the other one for objects and arrays.
Option Explicit
Private ScriptEngine As ScriptControl
Public Sub InitScriptEngine()
Set ScriptEngine = New ScriptControl
ScriptEngine.Language = "JScript"
ScriptEngine.AddCode "function getProperty(jsonObj, propertyName) { return jsonObj[propertyName]; } "
ScriptEngine.AddCode "function getKeys(jsonObj) { var keys = new Array(); for (var i in jsonObj) { keys.push(i); } return keys; } "
End Sub
Public Function DecodeJsonString(ByVal JsonString As String)
Set DecodeJsonString = ScriptEngine.Eval("(" + JsonString + ")")
End Function
Public Function GetProperty(ByVal JsonObject As Object, ByVal propertyName As String) As Variant
GetProperty = ScriptEngine.Run("getProperty", JsonObject, propertyName)
End Function
Public Function GetObjectProperty(ByVal JsonObject As Object, ByVal propertyName As String) As Object
Set GetObjectProperty = ScriptEngine.Run("getProperty", JsonObject, propertyName)
End Function
Public Function GetKeys(ByVal JsonObject As Object) As String()
Dim Length As Integer
Dim KeysArray() As String
Dim KeysObject As Object
Dim Index As Integer
Dim Key As Variant
Set KeysObject = ScriptEngine.Run("getKeys", JsonObject)
Length = GetProperty(KeysObject, "length")
ReDim KeysArray(Length - 1)
Index = 0
For Each Key In KeysObject
KeysArray(Index) = Key
Index = Index + 1
Next
GetKeys = KeysArray
End Function
Public Sub TestJsonAccess()
Dim JsonString As String
Dim JsonObject As Object
Dim Keys() As String
Dim Value As Variant
Dim j As Variant
InitScriptEngine
JsonString = "{""key1"": ""val1"", ""key2"": { ""key3"": ""val3"" } }"
Set JsonObject = DecodeJsonString(CStr(JsonString))
Keys = GetKeys(JsonObject)
Value = GetProperty(JsonObject, "key1")
Set Value = GetObjectProperty(JsonObject, "key2")
End Sub
Note:
The code uses early binding. So you have to add a reference to "Microsoft Script Control 1.0".
You have to call InitScriptEngine once before using the other functions to do some basic initialization.
Codo's answer is great and forms the backbone of a solution.
However, did you know VBA's CallByName gets you pretty far in querying a JSON structure. I've just written a solution over at Google Places Details to Excel with VBA for an example.
Actually just rewritten it without managing to use the functions adding to ScriptEngine as per this example. I achieved looping through an array with CallByName only.
So some sample code to illustrate
'Microsoft Script Control 1.0; {0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}; C:\Windows\SysWOW64\msscript.ocx
Option Explicit
Sub TestJSONParsingWithVBACallByName()
Dim oScriptEngine As ScriptControl
Set oScriptEngine = New ScriptControl
oScriptEngine.Language = "JScript"
Dim jsonString As String
jsonString = "{'key1':'value1','key2':'value2'}"
Dim objJSON As Object
Set objJSON = oScriptEngine.Eval("(" + jsonString + ")")
Debug.Assert VBA.CallByName(objJSON, "key1", VbGet) = "value1"
Debug.Assert VBA.CallByName(objJSON, "key2", VbGet) = "value2"
Dim jsonStringArray As String
jsonStringArray = "[ 1234, 4567]"
Dim objJSONArray As Object
Set objJSONArray = oScriptEngine.Eval("(" + jsonStringArray + ")")
Debug.Assert VBA.CallByName(objJSONArray, "length", VbGet) = "2"
Debug.Assert VBA.CallByName(objJSONArray, "0", VbGet) = "1234"
Debug.Assert VBA.CallByName(objJSONArray, "1", VbGet) = "4567"
Stop
End Sub
And it does sub-objects (nested objects) as well see Google Maps example at Google Places Details to Excel with VBA
EDIT: Don't use Eval, try to parse JSON safer, see this blog post
Super Simple answer - through the power of OO (or is it javascript ;)
You can add the item(n) method you always wanted!
my full answer here
Private ScriptEngine As ScriptControl
Public Sub InitScriptEngine()
Set ScriptEngine = New ScriptControl
ScriptEngine.Language = "JScript"
ScriptEngine.AddCode "Object.prototype.myitem=function( i ) { return this[i] } ; "
Set foo = ScriptEngine.Eval("(" + "[ 1234, 2345 ]" + ")") ' JSON array
Debug.Print foo.myitem(1) ' method case sensitive!
Set foo = ScriptEngine.Eval("(" + "{ ""key1"":23 , ""key2"":2345 }" + ")") ' JSON key value
Debug.Print foo.myitem("key1") ' WTF
End Sub
As Json is nothing but strings so it can easily be handled if we can manipulate it the right way, no matter how complex the structure is. I don't think it is necessary to use any external library or converter to do the trick. Here is an example where I've parsed json data using string manipulation.
Sub Json_data()
Const URL = "https://api.redmart.com/v1.5.8/catalog/search?extent=2&pageSize=6&sort=1&category=bakery"
Dim http As New XMLHTTP60, html As New HTMLDocument
Dim str As Variant
With http
.Open "GET", URL, False
.send
str = Split(.responseText, "category_tags"":")
End With
On Error Resume Next
y = UBound(str)
For i = 1 To y
Cells(i, 1) = Split(Split(str(i), "title"":""")(1), """")(0)
Cells(i, 2) = Split(Split(str(i), "sku"":""")(1), """")(0)
Cells(i, 3) = Split(Split(str(i), "price"":")(1), ",")(0)
Cells(i, 4) = Split(Split(str(i), "desc"":""")(1), """")(0)
Next i
End Sub
So its 2020 and yet due to lack of an end-to-end solution, I stumbled upon this thread. It did help but if we need to access the data without Keys at runtime dynamically, the answers above, still need a few more tweaks to get the desired data.
I finally came up with a function to have an end-to-end neat solution to this JSON parsing problem in VBA. What this function does is, it takes a JSON string(nested to any level) as input and returns a formatted 2-dimensional array. This array could further easily be moved to Worksheet by plain i/j loops or could be played around conveniently due to its easy index-based accessibility.
Sample input-output
The function is saved in a JSON2Array.bas file at my Github repo.
JSON2Array-VB
A demo usage subroutine is also included in the .bas file.
Please download and import the file in your VBA modules.
I hope it helps.
I know it's late, but for those who doesn't know how to use VBJSON, you just have to:
1) Import JSON.bas into your project (Open VBA Editor, Alt + F11; File > Import File)
2) Add Dictionary reference/class
For Windows-only, include a reference to "Microsoft Scripting Runtime"
You can also use the VBA-JSON the same way, which is specific for VBA instead of VB6 and has all the documentation.