I agree that having multiple different types of "object values" share one JSON key with no explicit "type" tag is asking for trouble with extensibility and conflicts.
That said, I think the constructive suggestion would be: "add a type tag to all objects in a union" (something suggested elsewhere in this thread).
Their "handles" can still claim "just a string" to save bandwidth in the common case, arrays can still represent "many things" and objects require "type" to be dis-ambiguous.
Most of the comments below don't mention the (real and important, but easily solvable) issue you've brought up however. They primarily fall into one of two buckets:
- It's hard to work with data shaped like this in my language (ex: java/go)
- It's hard to deserialize data shaped like this into my language that has no tagged unions (ex: java/go)
My biggest counterpoint to all of these complaints is: The fact that your language of choice cannot represent the concept of "one of these things" doesn't change the fact that this accurately describes reality sometimes.
A protocol with mutually exclusive keys (or really anything) by convention is strictly more bug-prone than a protocol with an object that is correct by construction.