I have successfully been able to serialize an entire class of properties (array, vectors of strings, integers, doubles). Deserialize also works, but I need a way to parse the returned object into the various arrays, and then set these equal to the four arrays of the same dimension.
Before, when I was using BinaryFormatter, I could simply declare mydeser As Object, deserialize into mydeser, and then pick off the arrays directly by using e.g. readarray = mydeser.array.
I did notice that a JObject is a type of dictionary, but I don't know what the keys would be or the values, which are some sort of jsonToken.
I tried using:
For each kvp as KeyValuePair(String, jsonToken) in myser
Next
but an exception was thrown. So is there a way to maybe use a key, and then the JObject's values directly into the arrays, using maybe readarray = mydeser("array") -- assuming mydeser is a dictionary?
Imports System.IO
Imports Newtonsoft.Json
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim ser As New testSer
End Sub
End Class
Public Class testSer
Public Property array As Double(,)
Public Property vector As Double()
Public Property strVec As String()
Public Property IntVec As Integer()
Sub New()
serdeser()
End Sub
Sub serdeser()
Dim r As New Random
ReDim array(1000, 1000)
ReDim vector(1000)
ReDim strVec(1000)
ReDim IntVec(1000)
For i = 1 To 1000
vector(i) = r.NextDouble
strVec(i) = "A"
IntVec(i) = r.Next(1, 100)
For j = 1 To 1000
array(i, j) = r.NextDouble
Next
Next
Dim jsonSerializer As New JsonSerializer
Dim stream As FileStream = File.Create("D:\test")
Dim writer As New StreamWriter(stream)
Dim jsonWriter As New JsonTextWriter(writer)
jsonSerializer.Serialize(jsonWriter, Me)
writer.Close()
Dim stream1 As FileStream = File.Open("D:\test", FileMode.Open, FileAccess.Read, FileShare.Read)
Dim reader As New StreamReader(stream1)
Dim jsonTextReader As New JsonTextReader(reader)
Dim mydeser As Linq.JObject
mydeser = jsonSerializer.Deserialize(jsonTextReader)
Dim readarray(1000, 1000) As Double
Dim readvector(1000) As Double
Dim readstrVec(1000) As String
Dim readIntVec(1000) As Integer
Dim mystr = DirectCast(mydeser.First, Linq.JProperty)
'readarray = mydeser.array <--need to assign "array" back into readarray
'readvector = mydeser.vector
'readstrVec = mydeser.strvec
'readarray = mydeser.intvec
reader.Close()
End Sub
End Class
A few suggestions, considering that this is a test class:
Your serialization procedure is mostly correct. It's preferable to declare the Stream objects with a Using statement, thus in case of an exception, these objects are implicitly disposed.
The deserialization becomes quite simple if you just specify the Type to deserialize to. The Type is represented by the Type of your class. You can just write (see the sample code):
Dim myObject = [JsonSerializer].Deserialize(Of [MyObjectType])([JsonTextReader])
You don't need a FileStream, StreamWriter / StreamReader already use a FileStream internally on their own. StreamWriter creates the file if it doesn't exist.
The StreamReader is initialized explicitly with Encoding.UTF8: it's the default, it's simply a remainder that this is the Encoding used and that the file is saved without BOM signature.
Call Close() on the JsonTextWriter before you exit the Using block. The behavior is determined by the AutoCompleteOnClose property, set to True by default and it's safer.
When deserializing, set the JsonTextReader's FloatParseHandling property to FloatParseHandling.Double or FloatParseHandling.Decimal. Double is actually the default, just keep this in mind, in case you need to handle Decimal values, which are otherwise deserialized as Double.
The lower bound of arrays is 0, not 1
Make your Random object a static Field. This ensures a better functionality (or, it lets the class function properly, if you want).
In the modified sample class, I've overloaded the Constructor: passing True, will call the Build() method that fills the class object with random data.
Passing False, generates an empty object.
It's not necessary to make a copy of the deserialized values, you can generate a new class object from the JsonSerializer directly. For example:
Dim jsonPath = "d:\test.json"
' Passing True to the Constuctor, calls the Build() method
Dim serTest = New SerializationTest(True)
' Serialize all property values to the specified File
serTest.Serialize(jsonPath)
' Creates a new SerializationTest objects and fills it
' deserializing the JSON previously saved calling Serialize()
Dim serTest2 = New SerializationTest(False).Deserialize(jsonPath)
You can compare the two objects, serTest and serTest2
In case a copy is needed, see at the bottom.
Modified class:
Public Class SerializationTest
Private Shared rnd As New Random()
Public Sub New()
Me.New(False)
End Sub
Public Sub New(useBuilder As Boolean)
If useBuilder Then Build()
End Sub
Public Property DblArray As Double(,)
Public Property DblVector As Double()
Public Property StringVector As String()
Public Property IntVector As Integer()
Public Sub Serialize(jsonPath As String)
Using stream As New StreamWriter(jsonPath)
Dim jWriter As New JsonTextWriter(stream)
Dim serializer As New JsonSerializer()
serializer.Serialize(jWriter, Me)
jWriter.Close()
End Using
End Sub
Public Function Deserialize(jsonPath As String) As SerializationTest
Using reader As New StreamReader(jsonPath, Encoding.UTF8)
Dim jReader As New JsonTextReader(reader) With {
.FloatParseHandling = FloatParseHandling.Double
}
Dim serializer As New JsonSerializer()
Dim deserialized = serializer.Deserialize(Of SerializationTest)(jReader)
jReader.Close()
Return deserialized
End Using
End Function
Private Sub Build()
ReDim DblArray(1000, 1000)
ReDim DblVector(1000)
ReDim StringVector(1000)
ReDim IntVector(1000)
For i = 0 To 1000
DblVector(i) = rnd.NextDouble()
StringVector(i) = $"A{i}"
IntVector(i) = rnd.Next(1, 101)
For j = 0 To 1000
DblArray(i, j) = rnd.NextDouble()
Next
Next
End Sub
End Class
If you, for some reason, want to copy the deserialized values to the initialized, but empty, class object, then change the method like this:
Public Sub Deserialize(jsonPath As String)
Using reader As New StreamReader(jsonPath, Encoding.UTF8)
Dim jReader As New JsonTextReader(reader) With {
.FloatParseHandling = FloatParseHandling.Double
}
Dim serializer As New JsonSerializer()
Dim deserialized = serializer.Deserialize(Of SerializationTest)(jReader)
jReader.Close()
Me.DblArray = deserialized.DblArray
Me.DblVector = deserialized.DblVector
Me.StringVector = deserialized.StringVector
Me.IntVector = deserialized.IntVector
deserialized = Nothing
End Using
End Sub
And change the code that fills it in:
Dim serTest = New SerializationTest(False)
serTest.Deserialize(jsonPath)
Related
I'm running into a little problem that I haven't found a way to to solve.
I haven't found a forum where this specific problem is addressed, I really hope to find some help.
Here is my code:
Imports System.IO
Imports System.Net
Imports Newtonsoft.Json.Linq
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim request As HttpWebRequest
Dim response As HttpWebResponse = Nothing
Dim reader As StreamReader
request = DirectCast(WebRequest.Create("https://pastebin.com/raw/dWjmfW8N"), HttpWebRequest)
response = DirectCast(request.GetResponse(), HttpWebResponse)
reader = New StreamReader(response.GetResponseStream())
Dim jsontxt As String
jsontxt = reader.ReadToEnd()
Dim myJObject = JObject.Parse(jsontxt)
For Each match In myJObject("matches")
Console.WriteLine(match("http")("host").ToString)
Next
End Sub
End Class
Here is the output:
223.16.205.13
190.74.163.58
71.7.168.29
117.146.53.244
31.170.146.28
118.36.122.169
123.7.117.78
113.61.154.182
36.48.37.191
113.253.179.234
124.13.29.41
180.122.74.183
121.157.114.93
39.78.35.216
176.82.1.100
201.143.142.75
222.117.29.229
89.228.209.185
59.153.89.245
148.170.162.37
112.160.243.23
62.101.254.177
190.141.161.149
121.132.177.79
79.165.124.174
118.39.91.43
220.83.82.58
220.161.101.195
190.218.188.86
123.241.174.77
219.71.218.113
81.198.205.2
1.64.205.1
190.204.66.180
203.163.241.36
36.34.148.33
221.124.127.89
115.29.210.231
39.121.63.13
178.160.38.191
117.146.55.217
149.91.99.49
220.93.231.104
49.245.71.40
211.44.70.107
37.119.247.51
222.101.54.200
178.163.102.223
119.198.145.129
188.26.240.141
115.29.233.160
190.164.29.145
94.133.185.144
181.37.196.134
116.88.213.9
115.2.194.11
1.226.12.161
178.63.73.210
49.149.194.242
14.32.29.251
59.0.191.68
58.122.168.43
142.129.230.137
105.145.89.51
201.243.97.65
175.37.162.102
186.88.141.126
105.148.43.100
60.179.173.21
69.115.51.207
90.171.193.132
14.64.76.165
121.127.95.80
175.211.168.48
99.240.74.72
58.153.174.2
119.77.168.142
121.170.47.232
58.243.20.124
199.247.243.234
47.111.76.211
93.72.213.251
218.32.44.73
220.83.90.204
119.158.102.20
95.109.55.204
106.5.19.223
190.199.215.69
190.218.57.249
36.102.72.163
219.78.162.215
177.199.151.96
196.93.125.34
211.58.150.166
180.131.163.40
93.156.97.81
159.89.22.81
130.0.55.156
186.93.202.111
195.252.44.173
What I want to do is to transfer that console output to my Textbox1.Text. Can anyone please show me a way to solve this?
A somewhat simplified method, using WebClient's DownloadStringTaskAsync to download the JSON.
You don't need special treatment here, strings that represent IpAddresses are just numbers and dots and the source encoding is probably UTF8.
After that, just parse the JSON and Select() the property values you care about, transform the resulting Enumerable(Of JToken) to an array of strings and set the array as the source of a TextBox.Lines property.
You can store the lines collection for any other use, in case it's needed.
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Using client As New WebClient()
Dim json = Await client.DownloadStringTaskAsync([The URL])
Dim parsed = JObject.Parse(json)
Dim lines = parsed("matches").
Where(Function(jt) jt("http") IsNot Nothing).
Select(Function(jt) jt("http")("host").ToString()).ToArray()
TextBox1.Lines = lines
End Using
End Sub
There's no need to transfer anything. If you want the data in a TextBox then put it in a TextBox. You can then output the same data using Console.WriteLine or Debug.WriteLine. You can use a loop:
Dim hosts As New List(Of String)
For Each match In myJObject("matches")
hosts.Add(match("http")("host").ToString())
Next
Dim text = String.Join(Environment.NewLine, hosts)
myTextBox.Text = text
Console.WriteLine(text)
You could also use LINQ:
Dim text = String.Join(Environment.NewLine, myJObject("matches").Select(Function(match) match("http")("host").ToString()))
myTextBox.Text = text
Console.WriteLine(text)
Alternative approach to display collection of things in Winforms are ListView, DataGridView or other collection controls depends on desired usage.
Add ListView control in designer and next code will fill it with received values.
Shared ReadOnly client As HttpClient = New HttpClient()
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim response As HttpResponseMessage =
Await client.GetAsync("https://pastebin.com/raw/dWjmfW8N")
response.EnsureSuccessStatusCode()
Dim jsonBody As String = Await response.Content.ReadAsStringAsync()
Dim myJObject = JObject.Parse(jsonBody)
ListView1.Items.Clear()
For Each match In myJObject("matches")
ListView1.Items.Add(match("http")("host").ToString)
Next
End Sub
I have a problem deserializing the following JSON string:
{"error":null,"id":1234,"result":[[["config.param1","111"],["config.param2","1222"]],"Config System",1234]}
My structure is:
Public Structure stuConResponse
Dim [Error] As String
Dim ID As String
Dim Result As List(Of stuSubResults)
End Structure
Public Structure stuSubResults
Public Property X1 As List(Of List(Of String))
Public Property X2 As String
Public Property X3 As String
End Structure
And my code is:
Dim JSonSettings As New Newtonsoft.Json.JsonSerializerSettings
JSonSettings.CheckAdditionalContent = True
JSonSettings.DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTime
JSonSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore
JSonSettings.FloatFormatHandling = Newtonsoft.Json.FloatFormatHandling.DefaultValue
JSonSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
Dim HeloResponse As Structures.stuConResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Structures.stuConResponse)(ReceivedData, JSonSettings)
I tried making "Results" the following:
1) A tuple of (list of (list of (string), string, string))
2) A list of (list of (string))
3) Other lists and tuples combined
For the life of me, I can't deserialise the "result" object whatsoever.
I have no problems reading the error and ID, but when Result comes in, I get the error that JSON can't do it.
I don't also mind if "result" can go into a string un-deserialised where I can do some manual logic, but that also don't work as JSON is trying to be too cleaver.
In other words, the problem is getting JSON to read "[[[X1,Y1],[X2,Y2],X3,X4]", notice that it's a list/array and that it does not have any Key-names which is where the problem is (I think).
It would be great to get your thoughts on this one.
Thanks
Dim JSONC = New JavaScriptSerializer().DeserializeObject(yourjson)
Debug.Print(JSONC("result")(0)(0)(0)) 'get result collection, then first element, then first object then first element of object
You decide what you want to do with the object.
You can convert ii to dictionary then get stuff with ("key") but if the data stays the same I see no point.
Finally, I solved it!, I had no idea that you can use IDICTIONARY to read the whole thing and then break it down into ILISTs...
For anyone who is having the same problem, here is the solution:
1) Code:
Dim I As Integer
Dim ConPolConfig As Structures.stuConPolSubscribeResponse = Nothing
Dim dicItems As IDictionary = Newtonsoft.Json.JsonConvert.DeserializeObject(Of IDictionary)(ReceivedData, JSonSettings)
ConPolConfig.ID = dicItems("id")
ConPolConfig.Error = dicItems("error")
If Not dicItems("result") Is Nothing Then
ConPolConfig.ConfigItems = New List(Of Dictionary(Of String, String))
Dim ConfigProperties As IList = dicItems("result")(0)
Dim ConfSysReader As String = dicItems("result")(1)
Dim Token As Integer = dicItems("result")(2)
Dim ParamKey As String
Dim ParamVal As String
Dim Dic As New Dictionary(Of String, String)
For I = 0 To ConfigProperties.Count - 1
ParamKey = ConfigProperties(I)(0)
ParamVal = ConfigProperties(I)(1)
Dic.Add(ParamKey, ParamVal)
ConPolConfig.ConfigItems.Add(Dic)
Next
End If
2) Structures:
Public Structure stuConPolSubscribeResponse
Dim [Error] As String
Dim ID As String
Dim ConfigItems As List(Of Dictionary(Of String, String))
End Structure
This works and does exactly what I am looking for as per my initial question. i.e. reading a list or lists where the master list has an additional 2 different elements (a string and an integer). the iDictionary would read the whole thing without any errors and then you could iterate it using an ILIST...
Don't ask me why the source JSON string is written in such a way... but now this works to read it...
... I need a coffee ...
How can I get the name of a key in a json file? I have a json that I'm parsing in VB.NET and one of the 'fields' have a dynamic name (it changes). What could I do to get the key name?
For example:
...
"one":{
"two":{
"example":[
{
"aaa":"test",
"bbb":"test",
"ccc":"test"
},
...
I'm getting correctly all the values (test, test, test...) and the keys 'one', 'two', have always the same name. But the key 'example' changes the name according the json file information. How could I identify the key text?
I wrote a piece of code that converts JSON into a XDocument here: https://github.com/dday9/.NET-JSON-Transformer
If you were to use that code, then you could get the node that represents your "two" object and then get the first child node in to. By doing this, you're essentially getting the array by an Index instead of by a name.
Here is a quick example of what I mean:
Dim literal As String = "{""two"":{""example"":[{""aaa"":""test"",""bbb"":""test"",""ccc"":""test""}]}}"
Dim xJSON As XDocument = JSON.Parse(literal)
Dim object_two As XElement = xJSON.Descendants("two").FirstOrDefault()
If object_two IsNot Nothing Then
Dim first_descendent As XElement = object_two.Descendants().Skip(1).FirstOrDefault()
If first_descendent IsNot Nothing Then
Console.WriteLine(first_descendent)
End If
End If
Fiddle: Live Demo
This piece will allow to get data from an unknown JSON structure, without having to define a class.
Sample
Dim serializer As System.Web.Script.Serialization.JavaScriptSerializer
serializer = New JavaScriptSerializer()
' {"elements":[{"handle~":{"emailAddress":"myself#example.com"},"handle":"urn:li:emailAddress:348955221"}]}
dim json as string
Dim obj As System.Collections.Generic.IDictionary(Of String, Object)
obj = serializer.Deserialize(Of System.Collections.Generic.IDictionary(Of String, Object))(json)
dim email as string=string.empty
email = If(GetJsonValue(obj, {"elements", "handle~", "emailAddress"}.ToList()), email)
The Function, very self descriptive:
''' <summary>decode json data </summary>
Public Function GetJsonValue(ByVal obj As Object,
ByVal key As List(Of String)) As String
GetJsonValue = Nothing
' If the object is an array, assume any element can contain the key
If obj.GetType Is GetType(Object()) Then
For Each newObj As Object In CType(obj, Object())
Dim tmp As String = GetJsonValue(newObj, key)
If Not String.IsNullOrEmpty(tmp) Then Return tmp
Next
Else
Dim objEle As System.Collections.Generic.IDictionary(Of String, Object)
Dim keyName As String
Dim objKey As String
'
keyName = key(0)
objEle = CType(obj, System.Collections.Generic.IDictionary(Of String, Object))
objKey = objEle.Keys.ToArray()(0)
If objEle.ContainsKey(keyName) Then
Dim temp As Object = objEle.Item(keyName)
If key.Count > 1 Then
' if the element is array, we need to get the array element and move to the next
key.RemoveAt(0)
Return GetJsonValue(temp, key)
Else
Return temp.ToString()
End If
End If
End If
End Function
I see this is solved but would like to suggest another solution for future readers. The JavaScriptSerializer can return a nested dictionary collection (Of String, Object). I find it easier to explore the result in debug while coding. The code below shows an example of how to navigate the collections.
Dim deserializer As New System.Web.Script.Serialization.JavaScriptSerializer
Dim text As String = "{""two"":{""example"":[{""aaa"":""test"",""bbb"":""test"",""ccc"":""test""}]}}"
Dim dict As Dictionary(Of String, Object) = deserializer.DeserializeObject(text)
Dim keys As Dictionary(Of String, Object).KeyCollection
keys = dict("two")("example")(0).Keys
Dim aaaName As String = keys(0)
Dim aaaValue As String = dict("two")("example")(0)(aaaName)
I am just trying to figure out the best way to deserialize a json string returned from a 3rd party api call. I read ServiceStack is fast so want to try it out. No experience and here is what I have done:
Opened Visual Studio 2013
Created new project Windows Forms Application
Installed ServiceStack.Text (based on https://servicestack.net/download)
Added a button (btnView) and textbox (txtOutput)
Add code to btnView_Click event
Private Sub btnView_Click(sender As Object, e As EventArgs) Handles btnView.Click
Me.Cursor = Cursors.WaitCursor
Dim wp As New WebPost 'this allows to pass url and return results
wp.URL = "xxxx"
Dim sJSONRetVal As String = wp.Request(String.Empty, True)
'sJSONRetVal return values looks like the following:
'{"complaints":[{"feedback_type":"abuse","subject":"Sales Agent Position"},{"feedback_type":"abuse","subject":"Sales Agent Position"}],"message":"OK","code":0}
'ServiceStack.Text example
Dim t As SMTP_Complaints = ServiceStack.Text.JsonSerializer.DeserializeFromString(Of SMTP_Complaints)(sJSONRetVal)
'For Each xi As SMTP_Complaints In t
' txtOutput.Text &= xi.mail_from & vbCrLf
'Next
wp = Nothing
txtOutput.Text = t.ToString
Me.Cursor = Cursors.Default
End Sub
Public Class SMTP_Complaints
Dim _feedback_type As String = ""
Dim _subject As String = ""
Public Property feedback_type As String
Get
Return _feedback_type
End Get
Set(value As String)
_feedback_type = value
End Set
End Property
Public Property subject As String
Get
Return _subject
End Get
Set(value As String)
_subject = value
End Set
End Property
End Class
The above doesn't seem to get any data. how would I loop through the data returned and return the data from both instances? Just not sure how I need to set this up to read the json data and then be able to output.
Based on the returned JSON of:
{"complaints":[{"feedback_type":"abuse","subject":"Sales Agent Position"},{"feedback_type":"abuse","subject":"Sales Agent Position"}],"message":"OK","code":0}
You will need two DTOs to deserialise this result.
I have used auto implemented properties here to simplify the complexity of the code. If you use an older version of VB, you'll need to expand these out to include a backing field with get and set method.
Public Class SMTP_Complaint
Public Property feedback_type As String
Public Property subject As String
End Class
Public Class SMTP_ComplaintsResponse
Public Property complaints As SMTP_Complaint()
Public Property message As String
Public Property code As Integer
End Class
You need the SMTP_ComplaintsResponse class because your complaints are wrapped in your JSON response.
Then to deserialise the response:
Dim response = JsonSerializer.DeserializeFromString(Of SMTP_ComplaintsResponse)(sJSONRetVal)
And your complaints are then accessible:
For Each complaint As var In response.complaints
Console.WriteLine("Type: {0}, Subject {1}", complaint.feedback_type, complaint.subject)
Next
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.