Why is my JSON object unset after I streamed values into it?
← JSON and Data Processing · Ref: Q1413
A JSON object can only take objects, and a single JSON value can take nothing. Pouring into JSON follows the target's shape: an unset or array target collects, an object target merges each streamed object in; anything else - a non-object into an object, or anything into a single value - is invalid, leaves the target unset, and later items do not revive it.
A MISMATCH IS INVALID
A scalar (a number, a String, a Boolean) or an array poured into an object has no key to merge under, so the operation is invalid. A single value has a nature and is not a container, so pouring anything into it is invalid too. EK9's answer to an invalid operation is UNSET - no exception, no half-merged object.
json? tells you whether the target survived:
settings <- JSON("theme", JSON("dark")) cat [JSON(7)] > settings stdout.println(`still set: ${settings?}`)
THE MISMATCH STICKS
Once a pour has gone wrong, the items after it are ignored - an object poured in after the mismatch does not start a new array - so a mismatch anywhere in the stream is still visible at the end. Only an explicit assignment ends it: ':=:', ':~:' (which takes its source, as it does into any unset JSON) or clear(). A '>' statement is all or nothing: it pours into a fresh JSON of the target's shape and replaces the target when the stream ends, so the target is left plain unset - the mismatch is visible at the end of that stream - and a later '>' or '>>' statement starts a fresh pour (into an unset target it collects an array). A mismatch made by '>>', which pours into the target itself, sticks until one of those assignments, or a '>' that replaces the target.
KEEP THE STREAM TO OBJECTS
Filter the stream down to objects before it reaches an object target:
isObject() as pure -> item as JSON <- rtn as Boolean: item.objectNature()
then cat items | filter by isObject >> settings.
THE NEXT POUR STARTS AGAIN
There is no error to catch - the target is simply unset, and an unset target collects the next pour into an array. An object poured in after the mismatch therefore comes back as an array of that object:
cat [JSON("age", JSON(30))] > settings stdout.println($settings)
prints [{"age":30}], not {"age":30}. To keep an object, pour into JSON().object() or an object you still hold.
To collect values of any shape, pour them into an unset JSON or an array instead (Q1411). See Q1412 for merging objects.
Example
#!ek9 defines module qa.jsonanddata.jsonpourmismatchunset defines function isObject() as pure -> item as JSON <- rtn as Boolean: item.objectNature() defines program JsonPourMismatchUnsetDemo() stdout <- Stdout() //A scalar has no key to merge under: the object is left unset scalarInto <- JSON("colour", JSON("red")) cat [JSON(7)] > scalarInto stdout.println(`scalar into object, still set: ${scalarInto?}`) //So does an array arrayInto <- JSON("font", JSON("serif")) cat [$$ [1, 2]] > arrayInto stdout.println(`array into object, still set: ${arrayInto?}`) //A single value has a nature and is not a container: nothing can be poured into it count <- JSON(1) cat [JSON(2)] > count stdout.println(`into a single value, still set: ${count?}`) //The mismatch sticks: an object after it does not start a new array sticky <- JSON("size", JSON(10)) cat [JSON(7), JSON("weight", JSON(3))] > sticky stdout.println(`object after the mismatch, still set: ${sticky?}`) //Keep the stream to objects before it reaches an object target settings <- JSON("theme", JSON("dark")) cat [JSON(7), JSON("size", JSON(12)), $$ [1, 2]] | filter by isObject >> settings stdout.println(`filtered: ${settings}`) //The target a mismatch left unset collects the next pour into an array: an object comes back as an array revived <- JSON("name", JSON("Ann")) cat [JSON(7)] > revived cat [JSON("age", JSON(30))] > revived stdout.println(`next pour into the unset target: ${revived}`)
Other ways to ask this
- What happens when I stream a number into a JSON object or a JSON value?
- Can I pour a JSON array into a JSON object?
- How do I stop non-objects spoiling a JSON object I stream into?
- Why does json? stay false for the rest of a pipeline?
Coming from another language?
Java: Jackson ObjectNode.setAll() only accepts an ObjectNode, so a value of the wrong shape is a compile error or a ClassCastException. JavaScript: Object.assign({}, 7) silently ignores the number. EK9: a value of the wrong shape poured into a JSON object, or anything poured into a single JSON value, makes it unset for the rest of the pour, visible with json?, and a filter keeps the stream to objects.
Keywords: filter, invalid, json, mismatch, object, objectNature, pipe, pour, stream, unset