Skip to content

fix(schema): apply additive JSON Schema semantics to $ref sibling getters#2921

Open
aqeelat wants to merge 2 commits into
microsoft:mainfrom
aqeelat:fix/schema-reference-sibling-semantics
Open

fix(schema): apply additive JSON Schema semantics to $ref sibling getters#2921
aqeelat wants to merge 2 commits into
microsoft:mainfrom
aqeelat:fix/schema-reference-sibling-semantics

Conversation

@aqeelat

@aqeelat aqeelat commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

OpenApiSchemaReference previously used reference-first fallback (Reference.X ?? Target?.X) for all keyword getters, which made additive assertion keywords look like overrides of the referenced schema.

Per JSON Schema 2020-12, assertion keywords alongside $ref are additive — both the sibling constraint and the target constraint apply. A sibling minProperties: 2 does not relax a target minProperties: 5.

This change revises the getter policy into three tiers based on feedback from @handrews in #2919.

Changes

Losslessly combinable scalars compute effective values:

Keyword Logic
MinLength, MinItems, MinProperties Math.Max(target, sibling)
MaxLength, MaxItems, MaxProperties Math.Min(target, sibling)
ReadOnly, WriteOnly true if either source is true
AdditionalPropertiesAllowed, UnevaluatedProperties false if either source is false

Non-lossless keywords remain reference-first convenience accessors:

  • Type, Format, Properties, Items, Required, OneOf/AnyOf/Not, If/Then/Else, etc.
  • Maximum/Minimum/ExclusiveMaximum/ExclusiveMinimum — raw JSON number strings, not parsed to avoid precision loss
  • Contains/MinContains/MaxContains — kept aligned as a group

Annotation keywords remain reference-first (unchanged):

  • Description, Title, Default, Deprecated, Examples, Extensions, ContentEncoding, etc.

Serialization is unchanged: $ref siblings still round-trip on JsonSchemaReference.

Design rationale

  • Non-lossless applicators (e.g. Properties, Items) cannot be collapsed into a single effective value without allOf composition. Target-preferred was considered but drops sibling constraints; reference-first keeps authored values visible and preserves setter/getter stability.
  • Raw numeric string bounds (maximum/minimum) are stored as strings to preserve arbitrary JSON number precision. Parsing to decimal for comparison risks choosing the wrong stricter bound on high-precision values, so these stay reference-first.
  • Required/AllOf merge attempts were reverted after review — mutable collection wrappers violated standard ISet/IList semantics (mutations disappeared). These stay reference-first.

Fixes #2919

OpenApiSchemaReference previously used reference-first fallback (Reference.X ?? Target?.X)
for all keyword getters. Per JSON Schema 2020-12, assertion keywords alongside  are
additive, not overrides. This change revises the getter policy into three tiers:

Losslessly combinable scalars compute effective values:
- MinLength/MinItems/MinProperties: Math.Max(target, sibling)
- MaxLength/MaxItems/MaxProperties: Math.Min(target, sibling)
- ReadOnly/WriteOnly: true if either source is true
- AdditionalPropertiesAllowed/UnevaluatedProperties: false if either is false

Non-lossless keywords remain reference-first convenience accessors:
- Type, Format, Properties, Items, Required, OneOf/AnyOf/Not, If/Then/Else, etc.
- Maximum/Minimum/ExclusiveMaximum/ExclusiveMinimum (raw JSON number strings,
  not parsed to avoid precision loss)
- Contains/MinContains/MaxContains (kept aligned as a group)

Annotation keywords remain reference-first (unchanged):
- Description, Title, Default, Deprecated, Examples, Extensions, ContentEncoding, etc.

Serialization is unchanged:  siblings still round-trip on JsonSchemaReference.

Fixes microsoft#2919
@aqeelat aqeelat requested a review from a team as a code owner July 2, 2026 11:46
@handrews

handrews commented Jul 2, 2026

Copy link
Copy Markdown

I'm not entirely sure the additionalProperties and unevaluatedProperties behaviors are correct. Both of those have behavior that is highly dependent on their exact schema location. In particular, additionalProperties can only be evaluated in terms of its immediate adjacent keywords in the same schema object. And for unevaluatedProperties, the behavior of placing in in the $ref source vs target is very different. Based purely on the comments (I haven't had time to try to understand the code- this is not an ecosystem with which I am familiar) this also seems to treat both as booleans, when either could also be a non-trivial schema of some sort.

…erence-first

Per feedback from @handrews on microsoft#2921: additionalProperties and
unevaluatedProperties are location-sensitive applicators whose behavior
depends on their exact schema location and adjacent keywords. They cannot
be safely collapsed into a single effective boolean.

Revert both from AND logic back to reference-first convenience accessors,
matching the non-lossless keyword bucket.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clarify or revise OpenApiSchemaReference semantics for $ref sibling keywords

2 participants