{jsonasoc:key1:val1:key2:val2:...}

Description

Builds a JSON object (an associative array) from an even, alternating list of key and value pairs: key1, value1, key2, value2, and so on. Keys are trimmed of surrounding whitespace; values are kept verbatim (leading and trailing spaces are preserved). A pair whose key is empty after trimming is skipped, and a final key with no value is dropped. The result is a compact json_encode string with no spaces and with slashes escaped. Its main use is to supply a named map where another command expects one: HTTP request headers for include, button labels for manager, or a const_arr for input and edit. Sibling commands: jsonarray builds a JSON array from a delimited list, and jsonstring quotes a single value as a JSON string.

Parameters

key required

An object key (a JSON property name). Surrounding whitespace is trimmed, so spaces around a key in a multi-line jsonasoc call do not end up in the key. If a key is empty after trimming, that whole key/value pair is skipped. When the same key appears twice, the last value wins. Keys are the odd-numbered parameters.

value required

The value paired with the preceding key. Values are stored verbatim - leading and trailing spaces are preserved (unlike keys). A value may itself be the output of another expression, for example a field-getter. Values are the even-numbered parameters; a final key left without a value is dropped.

Examples

test{jsonasoc:0:No:1:Yes}
Expected["No","Yes"]
Actual["No","Yes"]
A trap worth knowing: when the keys are exactly the integers 0, 1, 2 in order, the result encodes as a JSON array (the values only), not an object - this is how json_encode treats a list. So a const_arr keyed 0/1/2 loses its keys. If you need the numeric keys preserved as an object, start from 1 (1,2,3) or skip a number so the keys are not a perfect 0-based sequence.
test{jsonasoc:name:Jana:role:editor}
Expected{"name":"Jana","role":"editor"}
Actual{"name":"Jana","role":"editor"}
The basic shape: alternating key, value, key, value. The result is a compact JSON object with the keys and values quoted. There are no spaces in the output.
test{jsonasoc:status:active}
Expected{"status":"active"}
Actual{"status":"active"}
One key/value pair produces a one-property object. This is the minimal useful call.
test{jsonasoc: name : Jana Novak :role:editor}
Expected{"name":" Jana Novak ","role":"editor"}
Actual{"name":" Jana Novak ","role":"editor"}
Keys are trimmed of surrounding whitespace (so name with spaces around it becomes name), but values are stored exactly as given - the spaces around Jana Novak are preserved. This lets you lay a long jsonasoc call across several indented lines without spaces leaking into the keys.
test{jsonasoc::orphan:b:2:trailingkey}
Expected{"b":"2"}
Actual{"b":"2"}
A pair whose key is empty after trimming is skipped entirely (here the empty first key drops the value orphan). A final key with no value following it is also dropped (trailingkey). So this call yields only the b/2 pair. This is how jsonasoc stays robust when an optional pair expands to nothing.
test{jsonasoc:k:first:k:second}
Expected{"k":"second"}
Actual{"k":"second"}
If the same key appears more than once, the last value wins - the object cannot hold two properties of the same name.
test{jsonasoc:Content-Type:application/json}
Expected{"Content-Type":"application\/json"}
Actual{"Content-Type":"application\/json"}
json_encode escapes forward slashes, so application/json is stored as application\/json in the output. Both forms are valid JSON and parse to the same string; this is just how the value looks on the page. The same applies to any value containing a slash, such as a URL path.
virtual{include:https#://api.example.com/v1/subscribers?per_page=500:GET:{jsonasoc:Content-Type:application/json:key:API_KEY}}
Expected(the API response body; the headers object is {"Content-Type":"application\/json","key":"API_KEY"})
The canonical real-world use: include takes its request headers as a JSON object in the third parameter, and jsonasoc is the readable way to build it. Here it sends a Content-Type and an api key header with the GET request. Marked virtual because the output is whatever the remote API returns. The headers object alone expands to {"Content-Type":"application\/json","key":"API_KEY"}.
virtual{manager:a57e9c26eb1e04235696a56b05180f9c:relation........:{id..............}:_#ROW_EDIT::::{jsonasoc:N:Add game:D:Delete game}}
Expected(the manager UI with buttons labelled Add game / Delete game; the mode object is {"N":"Add game","D":"Delete game"})
Actual
manager accepts its mode either as flag letters (N=new, D=delete, ...) or as a jsonasoc object that maps each flag to a custom button label. Here N becomes Add game and D becomes Delete game. Marked virtual because the output is the rendered manager UI for the current item; the mode object alone is {"N":"Add game","D":"Delete game"}.
test{"const_arr":{jsonasoc:yes:Approved:no:Rejected}}
Expected{"const_arr":{"yes":"Approved","no":"Rejected"}}
Actual{"const_arr":{"yes":"Approved","no":"Rejected"}}
Commands like input and edit take a sel widgets settings as JSON, and the value-to-label map (const_arr) is an associative array - so jsonasoc builds it inline. Wrapping it as shown gives the settings object the widget expects. Note the keys here are yes / no, not 0 / 1 - see the next example for why that matters.