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