Retrieving a JSON element value that is an object (table)

{ "json_stanza":
  { "json-l0_object0":
    { 
      "json-l1_string0": "blah-0",
      "json-l1_string1": "blah-1",
      "json-l1_array0": [ 0, 1, 2, 3 ]
    },
    "json-l0_string0": "blah-0"
  }
}
json_get_keys _k
json_select $_k
json_get_keys _k
json_get_var _o $_k
echo $_o
J_T2

That is the variable 'stem' name of the json-l0_object0.

Should the _o variable not hold the object?

No JSON wizards in here?
ucode is being flogged as the OWRT gateway to JSON and yet expertise with the widely employed (now) legacy jshn.sh library is apparently thin on the ground.

Anyone? Anyone at all?

well i am afraid i am no wizard but i know if enough to answer your question..

the answer is No, the $_o variable will not hold the object because json_get_var only extracts primitive values ... strings, numbers, or booleans. It will not fetch or store the entire JSON object or array into a variable. Rather, jshn.sh parses JSON into an internal tree structure using memory pointers (represented by hash/stem names like J_T2 as you have already pointed out.

To access data inside your object, you must select into it using json_select.

usually something like this ...

# root object
json_select "json_stanza"

# move into the inner object using its key name
json_select "json-l0_object0"

# now inside json-l0_object0, get the internal keys:
json_get_keys inner_keys

# loop and print the strings inside
for key in $inner_keys; do
    # check the type to avoid printing the array as a blank string
    json_get_type key_type "$key"
    
    if [ "$key_type" = "string" ] || [ "$key_type" = "number" ]; then
        json_get_var value "$key"
        echo "$key: $value"
    fi
done

# step back out when you are done
json_select ..
json_select ..

you may find json_dump handy for debugging the structures.

Begging to disagree that a JSON element value of array or object types are not primitive, according to the documentation on json.org. That the OWRT shell wrapper library addresses complex JSON primitive types as tables speaks to the issue.

Thus, _o should contain the value of the element which indicates incomplete support for JSON.

Thanks for playing!

Thank you for enlightening me and correcting my json vocab. Rather than look into old tools for the reason this falls back to stem id, let me present you with a much better json library instead

local JSON = require "JSON"

json_stanza = JSON:object("json-l0_object0", JSON:object("json-l1_string0", "blah-0", "json-l1_string1", "blah-1", "json-l1_array0", JSON:array(0,1,2,3)), "json-l0_string0", "blah-0");

print(json_stanza, #json_stanza);
print(json_stanza["json-l0_object0"], #json_stanza["json-l0_object0"])

print(json_stanza:len())

for i = 0, #json_stanza["json-l0_object0"]["json-l1_array0"]-1 do
        print(json_stanza["json-l0_object0"]["json-l1_array0"][i])
end

print(json_stanza:tojson())
print(json_stanza:tolua())
print(json_stanza:tobash()) --> output in native Bash 4+ format 

output

object: 0x5d5b79e05850  2
object: 0x5d5b79dfcec0  3
130
0
1
2
3
{"json-l0_object0":{"json-l1_string0":"blah-0","json-l1_string1":"blah-1","json-l1_array0":[0,1,2,3]},"json-l0_string0":"blah-0"}
{json-l0_object0={json-l1_string0="blah-0",json-l1_string1="blah-1",json-l1_array0={0,1,2,3}},json-l0_string0="blah-0"}
([json-l0_object0]="([json-l1_string0]="blah-0" [json-l1_string1]="blah-1" [json-l1_array0]="(0 1 2 3)") [json-l0_string0]="blah-0")

api ref

EDIT
i noticed something writing the above script, i think it may be the root of your issue.

your string keys contian special characters, specifically "-" ... lua can not handle these keys in normal string key syntax ...ie some-value.another_value, the interperter chokes on the - so you must use the literal string syntax

json_stanza["json-l0_object0"] works
json_stanza.json-l0_object0  fails on -  

now i think jshn .sh does the same only for a different reason .. While Lua chokes on unquoted hyphens because it sees them as the subtraction operator, jshn.sh chokes because it uses the JSON keys to dynamically generate shell variable names, and hyphens are illegal in Bash/POSIX variable names.

so i think if you change

"json-l0_object0" to "json_l0_object0"

it will work as expected ??

The code snippit is unvarnished shell (ash). WYSIWYG. No gagging on permissible JSON characters.

As developers on the OWRT platform, the guiding principle of 'dancing with the one you brought', should be respected. That said, on any computing platform. Creating solutions that have a dependency on optional libraries / packages should be throughly vetted before the first LoC is typed, IMNSHO.

And decomposition of JSON text strings into a opaque and easily navigable set of shell environmental variables through jshn.sh is genius. It just lacks a smidgen of recursion.

this is incorrect about how jshn.sh works under the hood. While it is true that jshn.sh runs in unvarnished ash (the standard busybox shell), it is absolutely not WYSIWYG (What You See Is What You Get) for JSON objects.

When you parse JSON with jshn.sh, it uses a helper program (jshn) to convert the raw JSON text into dynamically generated shell variable names and memory pointers.

When you read your JSON, the framework does not store the text { "json-l1_string0": "blah-0", ... } inside a variable. Instead, it creates a series of flat environment variables that look like this inside the shell's memory:

  • _json_json_stanza=J_T1 (Pointer to the first object)
  • _json_json_stanza_json_l0_object0=J_T2 (Pointer to the second object)
  • _json_json_stanza_json_l0_string0=blah-0 (Actual string value)
  • _json_json_stanza_json_l0_object0_json_l1_string0=blah-0 (Actual string value)

Because of this specific architecture:

  1. Objects are only pointers: When you query json-l0_object0, the internal variable literally contains the string value J_T2. That is why json_get_var outputs J_T2.

  2. Character Restrictions exist: Because JSON keys are converted directly into shell variable names, jshn.sh actually will gag on certain permissible JSON characters. For example, if a JSON key contains a hyphen - or a dot ., standard shell variable naming rules break. jshn.sh has to internally map and sanitize these characters, which can lead to unexpected behavior if you try to use raw variable manipulation instead of the provided functions.

Apologies for the misapplication of English (and acronyms).
WYSIWYG refers to the output of the 'unvarnished' shell commands (implemented in the jshn.sh library).
Indeed, the helper executable, jshn massages / bridges invalid symbols with the prefixing of the global JSON_PREFIX variable into valid shell environmental variables.

That said, the 'solution' will recurse when encountering a type table whilst iterating over the 'array' AKA string returned by json_get_keys().

That testing the JSON element value's type as a determinant to branch (or not) is necessary is frankly, archaic.

Yes, exactly. To properly handle any arbitrary, deeply nested JSON structure, the solution must use a recursive function.

Whenever the iteration encounters a key where json_get_type returns object or array (which jshn.sh treats as internal pointer tables), the function must call itself recursively.

for example

#!/bin/sh
. /usr/share/libubox/jshn.sh

# The recursive function
walk_json() {
    local keys key key_type value i len

    # Get all keys at the current selection level
    json_get_keys keys

    for key in $keys; do
        json_get_type key_type "$key"

        case "$key_type" in
            object)
                # Move into the object table, recurse, then step back out
                json_select "$key"
                walk_json
                json_select ..
                ;;
            array)
                # Move into the array table
                json_select "$key"
                
                # Get the number of elements in the array
                json_get_keys len
                
                # Iterate through array indices (0, 1, 2...)
                for i in $len; do
                    json_get_type key_type "$i"
                    
                    if [ "$key_type" = "object" ] || [ "$key_type" = "array" ]; then
                        # Handle nested objects/arrays inside the array
                        json_select "$i"
                        walk_json
                        json_select ..
                    else
                        # Extract "primitive" (non json :) value from the array index
                        json_get_var value "$i"
                        echo "Array Element [$i]: $value"
                    fi
                done
                
                json_select ..
                ;;
            string|number|boolean)
                # Safely extract and print "primitive" types .. again primitive non json types
                json_get_var value "$key"
                echo "Key [$key]: $value"
                ;;
        esac
    done
}

# --- Example Usage ---

# Your JSON input
JSON_DATA='{ "json_stanza": { "json-l0_object0": { "json-l1_string0": "blah-0", "json-l1_string1": "blah-1", "json-l1_array0": [ 0, 1, 2, 3 ] }, "json-l0_string0": "blah-0" } }'

# Load text into the jshn memory tree
json_load "$JSON_DATA"

# Start the recursive walk from the root
walk_json

output

root@OpenWrt:~# ./json-test.sh
Key [json_l1_string0]: blah-0
Key [json_l1_string1]: blah-1
Array Element [1]: 0
Array Element [2]: 1
Array Element [3]: 2
Array Element [4]: 3
Key [json_l0_string0]: blah-0
root@OpenWrt:~# 

The need to address table(complex primitives) values in their entirety is to facilitate the splitting nested JSON tables into separate JSON texts, e.g., json-l0_object0:{}.

For this purpose the jshn.sh library is apparently ill-suited.

Apart from iterating over every table and reconstructing JSON text (laborious), are there other options (employing 'unvarnished' shell commands)?

There is jsonfilter - a lightweight C-compiled command-line tool explicitly built to bypass jshn.sh

You can target your nested strings or entire arrays straight into normal shell variables using unvarnished piping

# 1. Directly grab a deeply nested string value:
NESTED_STRING=$(echo "$JSON_DATA" | jsonfilter -e '$.json_stanza.json-l0_object0.json-l1_string0')
echo "$NESTED_STRING" # Outputs: blah-0

# 2. Directly dump a whole nested sub-object or array as a clean raw string:
NESTED_ARRAY=$(echo "$JSON_DATA" | jsonfilter -e '$.json_stanza.json-l0_object0.json-l1_array0')
echo "$NESTED_ARRAY" # Outputs: [ 0, 1, 2, 3 ]

I am no wizard though ... i just roll my own

local JSON = require "JSON"

local data = [[
{ "json_stanza":
  { "json-l0_object0":
    { 
      "json-l1_string0": "blah-0",
      "json-l1_string1": "blah-1",
      "json-l1_array0": [ 0, 1, 2, 3 ]
    },
    "json-l0_string0": "blah-0"
  }
}
]]

local obj = JSON:parse(data);
print(obj, #obj)

local arr = obj.json_stanza["json-l0_object0"]["json-l1_array0"]
print(arr, #arr)

for i = 0, #arr-1 do
    print(arr[i])
end

arr_copy = arr:unref()
print(arr, arr_copy)

arr_ref = arr:ref()
print(arr, arr_ref)
object: 0x6058d8799dc0  1
array: 0x6058d879b7d0   4
0
1
2
3
array: 0x6058d879b7d0   array: 0x6058d879c1a0
array: 0x6058d879b7d0   array: 0x6058d879b7d0

Good luck