Help traversing JSON via Dictionary

I have this working, but it’s sytactically bad code and I should really ask for a better way. I’m struggling with accessing the Dictionary element of the Data() array, without using a “For Each” statement to iterate over the Data() variable.

Is there any way to directly access the Dictionary nested inside Data()?

In the code snippet below I’m using a URLConnection to hit an API and getting back the JSON response that looks like this:

// [{“slot”:0,“io”:{“ai”:[{“aiIndex”:0,“aiMode”:2, …many more name-value pairs… ,“aiBurnoutValue”:2},{“aiIndex”:1,“aiMode”:2, …and so on…

What I really want to access is the collection of “ai” values. The following code works, but it seems overly complex, and iterating over an array of (1) element seems wasteful and misleading. Thanks for any suggestions.

Var Data() As Variant
Data = ParseJSON(json_string_from_api_here)

Var readings as Dictionary

For Each d As Dictionary In Data
  
  'System.DebugLog(d.Key(0).StringValue) ' "slot"
  'System.DebugLog(d.Key(1).StringValue) ' "io"
  
  Var searchString As String = d.Key(1).StringValue
  
  If searchString = "io" Then
    
    readings = d.Value("io")
    
  End If 
  
  
Next


Var io() As Variant

io = readings.Value("ai")


For Each reading As Dictionary in io
  
  Var aiValueScaled As Double = Double.FromString(reading.Value("aiValueScaled"))
  
  // "aiIndex":7,
  // "aiMode":2,
  // "aiValueRaw":13,
  // "aiValueScaled":0.008363999999999999907,
  // "aiValueRawMin":0,
  // "aiValueRawMax":62,
  // "aiValueScaledMin":0,
  // "aiValueScaledMax":0.039889000000000000845,
  // "aiResetMinValue":0,
  // "aiResetMaxValue":0,
  // "aiStatus":0,
  // "aiBurnoutValue":2
  
Next

I want to makes sure I understand the problem. In the JSON, there will only one Dictionary in the main array with the key “io” and you are looking for that one?

(BTW, d.Key(1) is not guaranteed to return the key you want as Dictionary keys have no guaranteed ordered. Use d.HasKey("io") instead.)

If I’m correct in my understanding, I think this will do what you want:

for each d as Dictionary in Data
    if d.HasKey( "io" ) then
        readings = d.Value( "io" )
        exit
    end if
next

if readings isa Dictionary then
    for each reading in Dictionary in readings.Value( "ai" )
        // Process
    next
end if

@Kem_Tekinay thanks for all this feedback!

I get a compiler error on this however…

for each d as Dictionary in Data
  if d.HasKey( "io" ) then
    readings = d.Value( "io" )
    exit
  end if
next

if readings isa Dictionary then
  for each reading in Dictionary in readings.Value( "ai" )
    // The line above gives compiler error:
    // "Syntax error for each reading in Dictionary in readings.Value( "ai" )"
  next
end if

Yes, that should be:

for each reading AS Dictionary in readings.Value( "ai" )

I appreciate your patience! But now I get:

This is not an array but you are using it as one for each reading AS Dictionary in readings.Value( "ai" )

Here’s what I’m using to generate test JSON (aka “content returned from api”)

Self.myJSONstring = "[{"+Chr(34)+"slot"+Chr(34)+":0,"+Chr(34)+"io"+Chr(34)+":{"+Chr(34)+"ai"+Chr(34)+":[{"+Chr(34)+"aiIndex"+Chr(34)+":0,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":0,"+Chr(34)+"aiValueScaled"+Chr(34)+":0,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":45,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":0.028951000000000001039,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":1,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":11993,"+Chr(34)+"aiValueScaled"+Chr(34)+":7.7158819999999996853,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":31089,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":20,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":2,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":6173,"+Chr(34)+"aiValueScaled"+Chr(34)+":3.9714949999999999974,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":6265,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":4.0306850000000000733,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":3,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":0,"+Chr(34)+"aiValueScaled"+Chr(34)+":0,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":39,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":0.025090999999999998721,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":4,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":0,"+Chr(34)+"aiValueScaled"+Chr(34)+":0,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":42,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":0.02702099999999999988,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":5,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":0,"+Chr(34)+"aiValueScaled"+Chr(34)+":0,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":46,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":0.029594999999999999779,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":6,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":0,"+Chr(34)+"aiValueScaled"+Chr(34)+":0,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":37,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":0.023804999999999999771,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2},{"+Chr(34)+"aiIndex"+Chr(34)+":7,"+Chr(34)+"aiMode"+Chr(34)+":2,"+Chr(34)+"aiValueRaw"+Chr(34)+":13,"+Chr(34)+"aiValueScaled"+Chr(34)+":0.008363999999999999907,"+Chr(34)+"aiValueRawMin"+Chr(34)+":0,"+Chr(34)+"aiValueRawMax"+Chr(34)+":62,"+Chr(34)+"aiValueScaledMin"+Chr(34)+":0,"+Chr(34)+"aiValueScaledMax"+Chr(34)+":0.039889000000000000845,"+Chr(34)+"aiResetMinValue"+Chr(34)+":0,"+Chr(34)+"aiResetMaxValue"+Chr(34)+":0,"+Chr(34)+"aiStatus"+Chr(34)+":0,"+Chr(34)+"aiBurnoutValue"+Chr(34)+":2}]}}]"

Just got to bed, I can look in the morning.

Can you run that code and paste the output instead?

Appreciate your time! Will paste output and hit the sack myself. Thanks!

21:58:19 : My Application Launched
21:58:21 : Window1.Button1.Pressed 0 aiValueScaled:0.00
         : Window1.Button1.Pressed 1 aiValueScaled:7.72
         : Window1.Button1.Pressed 2 aiValueScaled:3.97
         : Window1.Button1.Pressed 3 aiValueScaled:0.00
         : Window1.Button1.Pressed 4 aiValueScaled:0.00
         : Window1.Button1.Pressed 5 aiValueScaled:0.00
         : Window1.Button1.Pressed 6 aiValueScaled:0.00
         : Window1.Button1.Pressed 7 aiValueScaled:0.01
21:58:28 : My Application Ended

I think you need to cast the variant returned by readings.value, e.g.:

dim temp() as Dictionary
temp = readings.value("ai")
For each reading As Dictionary in temp
// stuff
Next

Unrelated question, but which is the preferred mechanism for dealing with JSON? New JsonItem(theString), which uses JsonItems to traverse? Or ParseJson, which uses dictionaries and variant arrays?

JsonItem would make your life so easier and readable. It looks like someone trying to make floating point operations using integers and lots of math.

BTW, I made your JSON string readable using an external formatter to people understand the struct:

Var myJson As String = _
"["+_
"  {"+_
"    ""slot"": 0,"+_
"    ""io"": {"+_
"      ""ai"": ["+_
"        {"+_
"          ""aiIndex"": 0,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 0,"+_
"          ""aiValueScaled"": 0,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 45,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 0.028951,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 1,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 11993,"+_
"          ""aiValueScaled"": 7.715882,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 31089,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 20,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 2,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 6173,"+_
"          ""aiValueScaled"": 3.971495,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 6265,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 4.030685,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 3,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 0,"+_
"          ""aiValueScaled"": 0,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 39,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 0.025091,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 4,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 0,"+_
"          ""aiValueScaled"": 0,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 42,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 0.027021,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 5,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 0,"+_
"          ""aiValueScaled"": 0,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 46,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 0.029595,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 6,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 0,"+_
"          ""aiValueScaled"": 0,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 37,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 0.023805,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        },"+_
"        {"+_
"          ""aiIndex"": 7,"+_
"          ""aiMode"": 2,"+_
"          ""aiValueRaw"": 13,"+_
"          ""aiValueScaled"": 0.008364,"+_
"          ""aiValueRawMin"": 0,"+_
"          ""aiValueRawMax"": 62,"+_
"          ""aiValueScaledMin"": 0,"+_
"          ""aiValueScaledMax"": 0.039889,"+_
"          ""aiResetMinValue"": 0,"+_
"          ""aiResetMaxValue"": 0,"+_
"          ""aiStatus"": 0,"+_
"          ""aiBurnoutValue"": 2"+_
"        }"+_
"      ]"+_
"    }"+_
"  }"+_
"]"

JSONItem uses ParseJSON under the hood, I’m told, so I prefer to cut out the middleman, but it’s really whichever makes you more comfortable.

2 Likes

Since JSONItem makes things a little more straightforward (you don’t get a Variant that could sometimes be an array or sometimes be a single item) and it’s much more obvious what you’re working with, that’s the route I prefer.

With the JSON provided, this is how you’d get to the ai children with JSONItem

var jsSource as new JSONItem(myJSONstring)

// jsSource is an array of items, so iterate to be sure we hit them all
for i as Integer = 0 to jsSource.LastRowIndex
  var jsThisItem as JSONItem = jsSource.ChildAt(i)
  
  // Get the "io" item
  var jsIO as JSONItem = jsThisItem.Child("io")
  
  // Get the "ai" item
  var jsAI as JSONItem = jsIO.Child("ai")
  
  // ai is also an array, so we'll iterate that
  for n as Integer = 0 to jsAI.LastRowIndex
    var jsAIItem as JSONItem = jsAI.Child(n)
    break
    
  next n
  
next i

With thanks to @Rick_Araujo for posting the cleaned up JSON code, this works:

var mainItems() as variant = ParseJSON( json )

var aiItems() as Variant

for each item as Dictionary in mainItems
  if item.HasKey( "io" ) then
    aiItems = item.Value( "io" )
    exit
  end if
next

for each aiItem as Dictionary in aiItems
  var aiIndex as integer = aiItem.Value( "aiIndex" )
  var aiMode as double = aiItem.Lookup( "aiMode", 0.0 )
  var aiValueRaw as double = aiItem.Lookup( "aiValueRaw", 0.0 )
  var aiValueScaled as double = aiItem.Lookup( "aiValueScaled", 0.0 )
  var aiValueRawMin as double = aiItem.Lookup( "aiValueRawMin", 0.0 )
  var aiValueRawMax as double = aiItem.Lookup( "aiValueRawMax", 0.0 )
  var aiValueScaledMin as double = aiItem.Lookup( "aiValueScaledMin", 0.0 )
  var aiValueScaledMax as double = aiItem.Lookup( "aiValueScaledMax", 0.0 )
  var aiResetMinValue as double = aiItem.Lookup( "aiResetMinValue", 0.0 )
  var aiResetMaxValue as double = aiItem.Lookup( "aiResetMaxValue", 0.0 )
  var aiStatus as double = aiItem.Lookup( "aiStatus", 0.0 )
  var aiBurnoutValue as double = aiItem.Lookup( "aiBurnoutValue", 0.0 )
  
  // Do something with these
next

Hi Kem,

This throws a TypeMismatchException at “aiItems = item.Value( "io" ):frowning:

for each item as Dictionary in mainItems
  if item.HasKey( "io" ) then
    aiItems = item.Value( "io" )
    exit
  end if
next

Have you tried my method? I ran it in the debugger with the JSON you provided and successfully reached the breakpoint :upside_down_face:

This throws a TypeMismatchException

This is why I don’t use Dictionary for parsing JSON.

As always, thanks @Rick_Araujo for clarifying my messiness. :slight_smile:

1 Like

Thank you @Tim_Parnell ! I’m leaning towards your opinion that Variants can be too tricky for the lean easy-to-follow flow of my code. This works exactly as I’d hoped.