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().

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.

To collect values of any shape, pour them into an unset JSON or an array instead (Q1411). See Q1412 for merging objects.

Example

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}`)
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: objectNature, filter, pour, unset, pipe, invalid, json, object, stream, mismatch