Related
I'm parsing a JSON string similar to the solution at this link: Parse JSON with VBA (Access 2010). However, I'm getting the "subscript out of range" error.
Public Sub GetValues()
Dim s As String, rates(), i As Long
s = "{""id"":""14acfa60-c0e1-47fb-8f80-ca0831bf3b52"",""class"":""us_equity"",""exchange"":""ARCA"",""symbol"":""UVXY"",""name"":"""",""status"":""active"",""tradable"":true,""marginable"":true,""shortable"":false,""easy_to_borrow"":false}"
rates = Array("id", "class", "exchange", "symbol", "name", "status", "tradeable", "marginable", "shortable", "easy_to_borrow")
For i = LBound(rates) To UBound(rates)
Debug.Print rates(i) & ":" & GetRate(s, rates(i))
Next i
End Sub
Public Function GetRate(ByVal s As String, ByVal delimiter As String) As String
GetRate = Replace(Split(Split(s, delimiter & Chr$(34) & Chr$(58))(1), Chr$(44))(0), Chr$(125), vbNullString)
End Function
You have a typo in your code:
Public Sub GetValues()
Dim s As String, rates(), i As Long
'Just for better reading.
's = "{""id"":""14acfa60-c0e1-47fb-8f80-ca0831bf3b52"", _
""class"":""us_equity"", _
""exchange"":""ARCA"", _
""symbol"":""UVXY"", _
""name"":"""", _
""status"":""active"", _
""tradable"":true, _
""marginable"":true, _
""shortable"":false, _
""easy_to_borrow"":false}"
'""tradable"":true, _ <<<<< ERROR in s var. In your rate array you say: "tradeable"
' "tradeable", _ <<<<< rate Array! (I just change it to run the code)
s = "{""id"":""14acfa60-c0e1-47fb-8f80-ca0831bf3b52"",""class"":""us_equity"",""exchange"":""ARCA"",""symbol"":""UVXY"",""name"":"""",""status"":""active"",""tradable"":true,""marginable"":true,""shortable"":false,""easy_to_borrow"":false}"
rates = Array("id", _
"class", _
"exchange", _
"symbol", _
"name", _
"status", _
"tradable", _
"marginable", _
"shortable", _
"easy_to_borrow")
For i = LBound(rates) To UBound(rates)
Debug.Print rates(i) & ":" & GetRate(s, rates(i))
Next i
End Sub
Public Function GetRate(ByVal s As String, ByVal delimiter As String) As String
'Chr$(34) = "
'Chr$(58) = :
'Chr$(125) = }
'Again... better reading.
Dim A: A = Split(s, delimiter & Chr$(34) & Chr$(58))(1)
Dim B: B = Split(A, Chr$(44))(0)
Dim C: C = Chr$(125)
GetRate = Replace(B, C, vbNullString)
End Function
First of all the issue in your code is that you have a typo: In your JSON you have tradable but your rate is called tradeable.
I recommend to include a proper error handling in your function. So if something gets wrong there you don't get stuck but a error message instead.
I also recommend not to have everything in one line in your function like Replace(Split(Split(… because if something gets wrong you don't know in which part it went wrong: First or second Split or the Replace. So if you do that in multiple lines (see below) then you can return a more useful error message.
Shorter code is not necessarily faster and better. But code that is easily readable, debugable and maintainable is very good code because you will make less errors and find them quicker.
I highly recommend to use meaningful variable names. Names like s for example are very bad names. If you use Json instead you always immediately see that this variable contains your JSON string.
Meaningful variables make your code better because it is more human readable and VBA doesn't care about the extra 3 characters.
Finally I would declare variables as close as possible to their first use.
So the code below is a bit longer but has much more improved readability and an error handling that gives at least a proper info if the key word you were looking for did not exist in your JSON.
Option Explicit
Public Sub GetValues()
Dim Json As String
Json = "{""id"":""14acfa60-c0e1-47fb-8f80-ca0831bf3b52"",""class"":""us_equity"",""exchange"":""ARCA"",""symbol"":""UVXY"",""name"":"""",""status"":""active"",""tradable"":true,""marginable"":true,""shortable"":false,""easy_to_borrow"":false}"
Dim Rates() As Variant
Rates = Array("id", "tradeable", "class", "exchange", "symbol", "name", "status", "tradeable", "marginable", "shortable", "easy_to_borrow")
Dim i As Long
For i = LBound(Rates) To UBound(Rates)
Debug.Print Rates(i) & ":" & GetRate(Json, Rates(i))
Next i
End Sub
Public Function GetRate(ByVal Key As String, ByVal Delimiter As String) As String
On Error GoTo RETURN_ERR
Dim SplitKey() As String
SplitKey = Split(Key, Delimiter & Chr$(34) & Chr$(58))
If UBound(SplitKey) = 0 Then
GetRate = "KEY NOT FOUND"
Exit Function
End If
Dim ValueOfKey As String
ValueOfKey = Split(SplitKey(1), Chr$(44))(0)
'remove } from value
ValueOfKey = Replace(ValueOfKey, Chr$(125), vbNullString)
'return
GetRate = ValueOfKey
Exit Function
RETURN_ERR:
GetRate = "Unknown error while extracting value. Check the JSON syntax."
End Function
I'm having trouble using the JSON-VBA converter with a multiple values key.
I have the normal recursion routines written to navigate JSON trees but here's an example of a simple JSON parse which I can't seem to get to work.
See this for the VBA-JSON converter software, which is terrific and fast.
Environment: Windows 7 / Access 2016 / Private LAN (no Internet)
Here's the code:
Option Compare Database
Option Explicit
Sub testparse()
Dim js As String, i As Long, jo As Object, item As Variant
Dim keys(), vals()
' fails on this string
js = "{ !Category!: !Famous Pets!," & _
"!code!: [!a!,!b!,!c!] }" ' string with multiple values
' with the following string, this works
js = "{ !Category!: !Famous Pets!," & _
" !code!: !singlecodevalue! }"
js = Replace(js, "!", Chr(34)) ' replace ! with quotes
Debug.Print " js = " & js
Set jo = JsonConverter.ParseJson(js) ' returns object with json elements
i = 0
ReDim keys(1 To jo.Count)
ReDim vals(1 To jo.Count)
Debug.Print " Number keys found at top level " & jo.Count
For Each item In jo
i = i + 1
keys(i) = item
vals(i) = jo(item)
Next item
For i = 1 To jo.Count
Debug.Print "key " & keys(i) & " = " & vals(i)
Next i
End Sub
For each item you encounter when running through a JSON object, you have to determine what you're dealing with -- especially if you don't know ahead of time how many items in an array! It gets even trickier if you have a compound JSON structure with collections inside arrays and such.
The bottom line is that you have to check each item you pull out of the JSON object and figure out what it is before accessing it. The top level of a JSON object (assuming the use of JsonConverter) will always be a Dictionary. So you can count on looping through the keys of the top level dictionary:
Dim json As Dictionary
Set json = JsonConverter.ParseJson(someJsonString)
Dim topLevelKey As String
For Each topLevelKey In json
Dim item As Variant
Debug.Print topLevelKey & " = " & item
Next topLevelKey
The problem with this is the item is not always a simple string. It can be a value (String), an array (Collection), or a group (Dictionary). See this answer as a good reference.
Basically, this means you have to check each item before you use it. So you can check it like this:
Select Case TypeName(item)
Case "Collection"
'--- loop through the item as a Collection
Case "Dictionary"
'--- loop through the item as a Dictionary
Case Else
'--- the item is a value of some type (String, Boolean, etc)
End Select
In my example here, I created a sub called ParseItem that checks each of the items in this manner. Reworking your original code into the example below:
Option Explicit
Sub testparse()
Dim js As String, i As Long, jo As Object, item As Variant
Dim keys(), vals()
' fails on this string
js = "{ !Category!: !Famous Pets!," & _
"!code!: [!a!,!b!,!c!] }" ' string with multiple values
' with the following string, this works
' js = "{ !Category!: !Famous Pets!," & _
' " !code!: !singlecodevalue! }"
'--- compound example
' js = "{ !Category!: !Famous Pets!,!code!: [!a!,!b!,{!c! : { !c1! : !1!, !c2!:!2!}}] }"
js = Replace(js, "!", Chr(34)) ' replace ! with quotes
Debug.Print "----------------------"
Debug.Print "js = " & js
Set jo = JsonConverter.ParseJson(js) ' returns object with json elements
ParseDictionary 1, "root", jo
End Sub
Private Sub ParseCollection(ByVal level As Long, _
ByVal key As String, _
ByRef jsonCollection As Variant)
Dim item As Variant
For Each item In jsonCollection
ParseItem level, key, item
Next item
End Sub
Private Sub ParseDictionary(ByVal level As Long, _
ByVal key As String, _
ByRef jsonDictionary As Variant)
Dim dictKey As Variant
For Each dictKey In jsonDictionary
ParseItem level, dictKey, jsonDictionary(dictKey)
Next dictKey
End Sub
Private Sub ParseItem(ByVal level As Long, _
ByVal key As String, _
ByRef item As Variant)
Select Case TypeName(item)
Case "Collection"
Debug.Print Format(level + 1, "00 ") & key & " (collection)"
ParseCollection (level + 1), key, item
Case "Dictionary"
Debug.Print Format(level + 1, "00 ") & key & " (dictionary)"
ParseDictionary (level + 1), key, item
Case Else
Debug.Print Format(level, "00 ") & key & " = " & item
End Select
End Sub
I am trying to learn about JSON in excel vba so bear me ..
This is JSON sample ..
{"Title":"Close-Up","Year":"1990","Rated":"NOT RATED","Released":"30 Oct 1991","Runtime":"98 min","Genre":"Documentary, Biography, Crime","Director":"Abbas Kiarostami","Writer":"Abbas Kiarostami","Actors":"Hossain Sabzian, Mohsen Makhmalbaf, Abolfazl Ahankhah, Mehrdad Ahankhah","Plot":"The true story of Hossain Sabzian that impersonated the director Mohsen Makhmalbaf to convince a family they would star in his so-called new film.","Language":"Persian, Azerbaijani","Country":"Iran","Awards":"2 wins.","Poster":"https://m.media-amazon.com/images/M/MV5BMzE4Mjc0MjI1N15BMl5BanBnXkFtZTcwNjI3MzEzMw##._V1_SX300.jpg","Ratings":[{"Source":"Internet Movie Database","Value":"8.3/10"},{"Source":"Rotten Tomatoes","Value":"88%"}],"Metascore":"N/A","imdbRating":"8.3","imdbVotes":"11,546","imdbID":"tt0100234","Type":"movie","DVD":"19 Feb 2002","BoxOffice":"N/A","Production":"Zeitgeist Films","Website":"http://www.zeitgeistfilm.com/current/closeup/closeup.html","Response":"True"}
This is in range("A1")
and I used this code to loop through each key and debug the key and its related value
Sub Test()
Dim ws As Worksheet
Dim jsonObject As Object
Dim item As Variant
Dim jsonText As String
Set ws = ThisWorkbook.Worksheets("Sheet1")
jsonText = ws.Cells(1, 1).Value
Set jsonObject = JsonConverter.ParseJson(jsonText)
For Each item In jsonObject.Keys
Debug.Print item & vbTab & jsonObject(item)
Next item
End Sub
The code works well in regular combinations of key and value but encountered an error at the key 'Ratings' as it is not as the others
How can I print the value of this key without nested loops. I mean to print this output
[{"Source":"Internet Movie Database","Value":"8.3/10"},{"Source":"Rotten Tomatoes","Value":"88%"}]
Thanks advanced for help
Sounds like you might want to stringify the values :
For Each item In jsonObject.Keys
Debug.Print item, Replace(JsonConverter.ConvertToJson(jsonObject(item)), """", "")
Next item
I would probably use a recursive sub to empty all the dictionaries including those inside the collection. It does have a level of nesting but it is minimal.
Public Sub GetInfoFromSheet()
Dim jsonStr As String, json As Object
jsonStr = [A1]
Set json = JsonConverter.ParseJson(jsonStr)
emptyDict json
End Sub
Public Sub emptyDict(ByVal json As Object)
Dim key As Variant, item As Object
For Each key In json
Select Case TypeName(json(key))
Case "String"
Debug.Print key & vbTab & json(key)
Case "Collection"
For Each item In json(key)
emptyDict item
Next
End Select
Next
End Sub
Examining your JSON structure:
You have an initial dictionary, denoted by {}, then within this a series of key and values pairs and a collection, denoted by []. That collection is made up also of dictionaries. So, I use a test with TypeName to determine if the top level dictionary value is String or Collection. If it is a Collection I recursively call the emptyDict sub to write out the results of the inner dictionaries.
To generate the string shown you only need what is in the collection:
Option Explicit
'[{"Source":"Internet Movie Database","Value":"8.3/10"},{"Source":"Rotten Tomatoes","Value":"88%"}]
Public Sub GetInfoFromSheet()
Dim jsonStr As String, json As Object, item As Object, output As String, key As Variant
jsonStr = [A1]
Set json = JsonConverter.ParseJson(jsonStr)("Ratings")
For Each item In json
For Each key In item.keys
If key = "Value" Then
output = output & "," & Chr$(34) & key & Chr$(34) & ":" & Chr$(34) & item(key) & Chr$(34) & "}"
Else
output = output & ",{" & Chr$(34) & key & Chr$(34) & ":" & Chr$(34) & item(key) & Chr$(34)
End If
Next key
Next
output = "[" & Replace$(output, ",", vbNullString, , 1) & "]"
Debug.Print output
End Sub
I got a simple json
{"200567175963759": { "pair": "esp_btc", "type": "sell", "amount": 2000000, "rate": 1E-08, "timestamp_created": "1498114417", "status": 0}}
I want to parse it without creating any new classes. I want to make it easy.
I am using jsonhelper class that I created my self to parse it. It's basically try to find the first thing between two double quotes and got 200567175963759 which is the order id. Get parameter is simply finding something between "pair":" and "
For simple json it works fine. How can I do get order ID, which si 200567175963759, or timestamp, using better parser, like newtonsoft.
I wonder if I can do that using newtonsoft json?
Dim jsonstring = jsonHelper.stripWhiteSpace(order3.ToString) '{"200567175963759": { "pair": "esp_btc", "type": "sell", "amount": 2000000, "rate": 1E-08, "timestamp_created": "1498114417", "status": 0}}
Dim orderid = fGetToken(order3.ToString, 1, """", """")
Dim base = b
Dim quote = key
Dim typeOfOrder = jsonHelper.getParameter(jsonstring, "type")
Dim amount = jsonHelper.getParameter(jsonstring, "amount")
Dim rate = jsonHelper.getParameter(jsonstring, "rate")
Dim timestamp_created = jsonHelper.getParameter(jsonstring, "timestamp_created")
Dim order4 = OrdersAtExchange.createOrders(amount, base, quote, _exchange, timestamp_created, rate, orderid)
_orders.Add(order4)
If I try to parse that using newtonsoft, I got this object whose type is
Dim order = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonorders)
Dim order1 = CType(order, Newtonsoft.Json.Linq.JObject)
Dim order2 = order1.Item("return").ToList
I look at all the method in Newtonsoft.Json.Linq.JObject I can't find anything that say convert dictionary structures in json to say generic.dictionary
There is something like that. I tried but simply didn't work.
So I wonder if there's an actual sample of some code parsing that simple json with newtonsoft?
Object is Type Dictionary, In case if property looks like index or key it probably dictionary
Dim JsonString As String = "{""200567175963759"": { ""pair"": ""esp_btc"", ""type"": ""sell"", ""amount"": 2000000, ""rate"": 1E-08, ""timestamp_created"": ""1498114417"", ""status"": 0}}"
Dim JsonSettings = New Newtonsoft.Json.JsonSerializerSettings
JsonSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
Dim OutObject = Newtonsoft.Json.JsonConvert.DeserializeObject(Of Dictionary(Of String, SampleType))(JsonString)
Class SampleType
Property pair As String
Property type As String
Property amount As String
Property rate As String
Property timestamp_created As String
Property status As String
End Class
Here is a sample showing how you can parse your JSON using Json.Net's LINQ-to-JSON API (JTokens, JObjects, etc.)
Dim json As String =
"{" &
" ""200567175963759"": {" &
" ""pair"": ""esp_btc""," &
" ""type"": ""sell""," &
" ""amount"": 2000000," &
" ""rate"": 1E-08," &
" ""timestamp_created"": ""1498114417""," &
" ""status"": 0" &
" }" &
"}"
Dim rootObject As JObject = JObject.Parse(json)
For Each prop As JProperty In rootObject.Properties()
Dim orderid As String = prop.Name
Dim orderInfo As JObject = prop.Value
Dim pair As String = orderInfo("pair").ToString()
Dim typeOfOrder As String = orderInfo("type").ToString()
Dim amount As Decimal = orderInfo("amount").ToObject(Of Decimal)
Dim rate As Decimal = orderInfo("rate").ToObject(Of Decimal)
Dim timestamp_created As String = orderInfo("timestamp_created").ToString()
Dim status As Integer = orderInfo("status").ToObject(Of Integer)
'etc. ...
Next
Demo: https://dotnetfiddle.net/X9SPIE
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