An add-in contributes its keywords by $extends-ing a Core definition — #/definitions/Property, #/definitions/ObjectType, and so on — and implementations enforce that the annotated node is of the extended kind. A property whose type is a union is of no single kind, so no add-in keyword can attach to it.
Repro
{
"$schema": "https://json-structure.org/meta/extended/v0/#",
"$id": "https://example.com/uniontest",
"$uses": ["JSONStructureUnits"],
"name": "T",
"type": "object",
"properties": {
"a": { "type": ["double", "null"], "unit": "m" },
"b": { "type": "double", "unit": "m" }
}
}
$ json-structure-check --extended uniontest.json
Schema is invalid:
- #/properties/a/unit (8:48) [SCHEMA_CONSTRAINT_TYPE_MISMATCH] 'unit' can only appear in numeric schemas.
Property b is fine. Property a differs only in being nullable.
Why it matters
["T", "null"] is the idiomatic way to say "a T that may be absent-but-present" in a schema derived from a feed that emits null for an unreported channel, and unreported channels are the normal case in observational data. The choice an author is left with is:
- keep the union and lose the annotation, so the number carries no unit; or
- drop
null from the union, leave the member out of required, and state in prose that an unreported channel is omitted rather than nulled — which changes the wire format the schema describes.
I took the second option across roughly thirty members of a sample corpus. It is a real change to the contract, made for a reason that has nothing to do with the data.
The same applies to any add-in keyword, not just unit: symbol from Units, and every annotation contributed by an extension whose add-in extends a scalar type definition.
Proposal
Define how add-in applicability is computed for a union type. The narrow rule that covers the motivating case:
An add-in whose $extends names a type definition T applies to a schema whose type is an array if every member of that array other than "null" is T.
That makes ["double", "null"] accept everything double accepts, and leaves genuinely heterogeneous unions such as ["double", "string"] rejected, which is the right answer — there is no single numeric interpretation to attach a unit to.
An alternative, if the union rule is felt to be too loose: give Core a way to write "nullable T" that preserves the scalar type identity, so that the annotation has an unambiguous subject. That is a larger change but it also removes the ambiguity from the many other places where ["T", "null"] is treated as a union rather than as an optionality marker.
Whichever is chosen, the current situation — where adding "null" to a type silently removes the author's ability to say what the number means — should not be the answer.
An add-in contributes its keywords by
$extends-ing a Core definition —#/definitions/Property,#/definitions/ObjectType, and so on — and implementations enforce that the annotated node is of the extended kind. A property whose type is a union is of no single kind, so no add-in keyword can attach to it.Repro
{ "$schema": "https://json-structure.org/meta/extended/v0/#", "$id": "https://example.com/uniontest", "$uses": ["JSONStructureUnits"], "name": "T", "type": "object", "properties": { "a": { "type": ["double", "null"], "unit": "m" }, "b": { "type": "double", "unit": "m" } } }Property
bis fine. Propertyadiffers only in being nullable.Why it matters
["T", "null"]is the idiomatic way to say "a T that may be absent-but-present" in a schema derived from a feed that emitsnullfor an unreported channel, and unreported channels are the normal case in observational data. The choice an author is left with is:nullfrom the union, leave the member out ofrequired, and state in prose that an unreported channel is omitted rather than nulled — which changes the wire format the schema describes.I took the second option across roughly thirty members of a sample corpus. It is a real change to the contract, made for a reason that has nothing to do with the data.
The same applies to any add-in keyword, not just
unit:symbolfrom Units, and every annotation contributed by an extension whose add-in extends a scalar type definition.Proposal
Define how add-in applicability is computed for a union type. The narrow rule that covers the motivating case:
That makes
["double", "null"]accept everythingdoubleaccepts, and leaves genuinely heterogeneous unions such as["double", "string"]rejected, which is the right answer — there is no single numeric interpretation to attach a unit to.An alternative, if the union rule is felt to be too loose: give Core a way to write "nullable T" that preserves the scalar type identity, so that the annotation has an unambiguous subject. That is a larger change but it also removes the ambiguity from the many other places where
["T", "null"]is treated as a union rather than as an optionality marker.Whichever is chosen, the current situation — where adding
"null"to a type silently removes the author's ability to say what the number means — should not be the answer.