<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<!-- generated by https://github.com/cabo/kramdown-rfc version 1.7.29 (Ruby 3.4.4) -->
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" ipr="trust200902" docName="draft-vasters-json-structure-cond-composition-00" category="std" consensus="true" submissionType="IETF" tocInclude="true" sortRefs="true" symRefs="true" version="3">
  <!-- xml2rfc v2v3 conversion 3.29.0 -->
  <front>
    <title>JSON Structure: Conditional Composition</title>
    <seriesInfo name="Internet-Draft" value="draft-vasters-json-structure-cond-composition-00"/>
    <author fullname="Clemens Vasters">
      <organization>Microsoft Corporation</organization>
      <address>
        <email>clemensv@microsoft.com</email>
      </address>
    </author>
    <date year="2025" month="July" day="02"/>
    <area>Web and Internet Transport</area>
    <workgroup>Building Blocks for HTTP APIs</workgroup>
    <keyword>Internet-Draft</keyword>
    <abstract>
      <?line 42?>

<t>This document specifies JSON Structure Conditional Composition, an extension to
JSON Structure Core that introduces composition constructs for combining multiple
schema definitions. In particular, this specification defines the semantics,
syntax, and constraints for the keywords <tt>allOf</tt>, <tt>anyOf</tt>, <tt>oneOf</tt>, and <tt>not</tt>,
as well as the <tt>if</tt>/<tt>then</tt>/<tt>else</tt> conditional construct.</t>
    </abstract>
    <note removeInRFC="true">
      <name>About This Document</name>
      <t>
        The latest revision of this draft can be found at <eref target="https://json-structure.github.io/conditional-composition/draft-vasters-json-structure-cond-composition.html"/>.
        Status information for this document may be found at <eref target="https://datatracker.ietf.org/doc/draft-vasters-json-structure-cond-composition/"/>.
      </t>
      <t>
      </t>
      <t>Source for this draft and an issue tracker can be found at
        <eref target="https://github.com/json-structure/conditional-composition"/>.</t>
    </note>
  </front>
  <middle>
    <?line 52?>

<section anchor="introduction">
      <name>Introduction</name>
      <t>This document specifies JSON Structure Conditionals, an extension to JSON Structure Core <xref target="JSTRUCT-CORE"/> that introduces conditional composition constructs for combining multiple schema definitions. In particular, this specification defines the semantics, syntax, and constraints for the keywords <tt>allOf</tt>, <tt>anyOf</tt>, <tt>oneOf</tt>, and <tt>not</tt>, as well as the <tt>if</tt>/<tt>then</tt>/<tt>else</tt> conditional construct.</t>
    </section>
    <section anchor="terminology-and-conventions">
      <name>Terminology and Conventions</name>
      <t>The key words MUST, MUST NOT, SHALL, SHALL NOT, REQUIRED, SHOULD, and OPTIONAL are to be interpreted as described in <xref target="RFC2119"/> and <xref target="RFC8174"/>.</t>
      <t>Unless otherwise specified, all references to "non-schema" refer to a JSON Structure Core non-schema object, which is an inert JSON object that does not declare a type constraint.</t>
    </section>
    <section anchor="composition-and-evaluation-model">
      <name>Composition and Evaluation Model</name>
      <t>The keywords introduced in this document extend the set of keywords allowed for non-schemas and schemas as defined in JSON Structure Core. In particular, the keywords defined herein MAY extend all non-schema and schema definitions.</t>
      <t>The focus of JSON Structure Core is on data definitions. The conditional composition keywords introduced in this document allow authors to define conditional matching rules that use these fundamental data definitions.</t>
      <t>A schema document using these keywords is not a data definition but a rule set for evaluating JSON node instances against schema definitions and lays the groundwork for validation.</t>
      <t>Fundamentally, evaluating a JSON node against a schema involves matching the node against the schema's constraints.</t>
      <t>The outcome of evaluating a JSON node against a schema is ultimately a boolean value that states whether the node met all constraints defined in the schema. The evaluation also creates an understanding of which constraint was met for each subschema during evaluation.</t>
      <t>A schema evaluation engine traverses the given JSON node and the schema definition, evaluating the node and the schema recursively. When a conditional composition keyword is encountered, the engine evaluates each subschema independently against the current node and then combines the results as specified by the composition keyword.</t>
    </section>
    <section anchor="conditional-composition-keywords">
      <name>Conditional Composition Keywords</name>
      <t>This section defines several composition keywords that combine schema definitions with evaluation rules. Each keyword has a specific evaluation semantics that determines the outcome of the validation process.</t>
      <section anchor="allOf">
        <name><tt>allOf</tt></name>
        <t>The value of the <tt>allOf</tt> keyword MUST be a type-union array containing at least one schema object. A JSON node is valid against <tt>allOf</tt> if and only if it is valid against every schema in the array.</t>
        <t>Consider the following non-schema, which does not define its own type but rather contains an <tt>allOf</tt> keyword with three subschemas:</t>
        <sourcecode type="json"><![CDATA[
{
  "allOf": [
    {
      "type": "object",
      "properties": {
        "a": { "type": "string" }
      },
      "required": ["a"],
      "additionalProperties": true
    },
    {
      "type": "object",
      "properties": {
        "b": { "type": "number" }
      },
      "required": ["b"],
      "additionalProperties": true
    },
    {
      "type": "object",
      "properties": {
        "c": { "type": "boolean" }
      },
      "required": ["c"],
      "additionalProperties": true
    }
  ]
}
]]></sourcecode>
        <t>Here, a JSON node evaluates to <tt>true</tt> if it is an object with at least three properties <tt>a</tt>, <tt>b</tt>, and <tt>c</tt>, where <tt>a</tt> is a string, <tt>b</tt> is a number, and <tt>c</tt> is a boolean:</t>
        <sourcecode type="json"><![CDATA[
{
  "a": "string",
  "b": 42,
  "c": true
}
]]></sourcecode>
        <t>The JSON node satisfies all constraints defined by all subschemas. Conflicting constraints among subschemas result in an unsatisfiable schema—for example, if two subschemas require the same property to have different types or if one of the subschemas has <tt>additionalProperties</tt> set to <tt>false</tt>.</t>
      </section>
      <section anchor="anyOf">
        <name><tt>anyOf</tt></name>
        <t>The value of the <tt>anyOf</tt> keyword MUST be a type-union array containing at least one schema object. A JSON node is valid against <tt>anyOf</tt> if and only if it is valid against at least one of the schemas in the array.</t>
        <t>Consider the following schema:</t>
        <sourcecode type="json"><![CDATA[
{
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "a": { "type": "string" }
      },
      "required": ["a"],
      "additionalProperties": true
    },
    {
      "type": "object",
      "properties": {
        "b": { "type": "number" }
      },
      "required": ["b"],
      "additionalProperties": true
    },
    {
      "type": "object",
      "properties": {
        "c": { "type": "boolean" }
      },
      "required": ["c"],
      "additionalProperties": true
    }
  ]
}
]]></sourcecode>
        <t>Here, a JSON node evaluates to <tt>true</tt> if it is an object with at least one of the properties <tt>a</tt>, <tt>b</tt>, or <tt>c</tt>, where <tt>a</tt> is a string, <tt>b</tt> is a number, and <tt>c</tt> is a boolean:</t>
        <sourcecode type="json"><![CDATA[
{
  "a": "string"
}
]]></sourcecode>
        <t>or</t>
        <sourcecode type="json"><![CDATA[
{
  "b": 42,
  "c": true
}
]]></sourcecode>
        <t>Both JSON nodes satisfy the constraints defined by at least one subschema.</t>
      </section>
      <section anchor="oneOf">
        <name><tt>oneOf</tt></name>
        <t>The value of the <tt>oneOf</tt> keyword MUST be a type-union array containing at least one schema object. A JSON node is valid against <tt>oneOf</tt> if and only if it is valid against exactly one of the schemas in the array.</t>
        <t>Consider the following schema:</t>
        <sourcecode type="json"><![CDATA[
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "a": { "type": "string" }
      },
      "required": ["a"],
      "additionalProperties": true
    },
    {
      "type": "object",
      "properties": {
        "b": { "type": "number" }
      },
      "required": ["b"],
      "additionalProperties": true
    },
    {
      "type": "object",
      "properties": {
        "c": { "type": "boolean" }
      },
      "required": ["c"],
      "additionalProperties": true
    }
  ]
}
]]></sourcecode>
        <t>Here, a JSON node evaluates to <tt>true</tt> if it is an object with exactly one of the properties <tt>a</tt>, <tt>b</tt>, or <tt>c</tt>, where <tt>a</tt> is a string, <tt>b</tt> is a number, and <tt>c</tt> is a boolean:</t>
        <sourcecode type="json"><![CDATA[
{
  "a": "string"
}
]]></sourcecode>
        <t>The following JSON node evaluates to <tt>false</tt> because it matches two subschemas:</t>
        <sourcecode type="json"><![CDATA[
{
  "a": "string",
  "b": 42
}
]]></sourcecode>
      </section>
      <section anchor="not">
        <name><tt>not</tt></name>
        <t>The value of the keyword <tt>not</tt> is a single schema object, which MAY be a type union. A JSON node is valid against <tt>not</tt> if it is not valid against the schema. For example, the schema is written as follows:</t>
        <sourcecode type="json"><![CDATA[
{
  "not": { "type": "string" }
}
]]></sourcecode>
        <t>Here, a JSON node evaluates to <tt>true</tt> if it is not a string:</t>
        <sourcecode type="json"><![CDATA[
42
]]></sourcecode>
      </section>
      <section anchor="if-then-else">
        <name><tt>if</tt>/<tt>then</tt>/<tt>else</tt></name>
        <t>The values of the keywords <tt>if</tt>, <tt>then</tt>, and <tt>else</tt> are schema objects. If the processed JSON node is valid against the <tt>if</tt> schema, the <tt>then</tt> schema further constrains the JSON node and MUST match the input. If the processed JSON node is not valid against the <tt>if</tt> schema, the <tt>else</tt> schema further constrains the JSON node and MUST match the input.</t>
        <t>Consider the following schema:</t>
        <sourcecode type="json"><![CDATA[
{
  "if": {
    "properties": {
      "a": { "type": "string" }
    },
    "required": ["a"]
  },
  "then": {
    "properties": {
      "b": { "type": "number" }
    },
    "required": ["b"]
  },
  "else": {
    "properties": {
      "c": { "type": "boolean" }
    },
    "required": ["c"]
  }
}
]]></sourcecode>
        <t>Here, a JSON node evaluates to <tt>true</tt> if it is an object with a property <tt>a</tt> that is a string; then it must also have a property <tt>b</tt> that is a number:</t>
        <sourcecode type="json"><![CDATA[
{
  "a": "string",
  "b": 42
}
]]></sourcecode>
        <t>Otherwise, if the JSON node does not have a property <tt>a</tt> that is a string, it must have a property <tt>c</tt> that is a boolean:</t>
        <sourcecode type="json"><![CDATA[
{
  "c": true
}
]]></sourcecode>
        <t>or</t>
        <sourcecode type="json"><![CDATA[
{
  "a": 42,
  "c": false
}
]]></sourcecode>
      </section>
      <section anchor="enabling-the-extensions">
        <name>Enabling the Extensions</name>
        <t>The conditional composition extensions can be enabled in a schema or meta-schema
by adding the <tt>JSONSchemaConditionalComposition</tt> key to the <tt>$uses</tt> clause when
referencing the extended meta-schema:</t>
        <sourcecode type="json"><![CDATA[
{
  "$schema": "https://json-structure.org/meta/extended/v0/#",
  "$id": "myschema",
  "$uses": [
    "JSONSchemaConditionalComposition"
  ],
  "oneOf" : [
    { "type": "string" },
    { "type": "number" }
  ]
}
]]></sourcecode>
        <t>Conditional composition is enabled by default in the validation meta-schema:</t>
        <sourcecode type="json"><![CDATA[
{
  "$schema": "https://json-structure.org/meta/validation/v0/#",
  "$id": "myschema",
  "type": "object",
  "oneOf" : [
    { "type": "string" },
    { "type": "number" }
  ]
}
]]></sourcecode>
      </section>
    </section>
    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <ul spacing="normal">
        <li>
          <t>The use of composition keywords does not alter the security model of JSON Structure Core; however, excessive nesting or overly complex compositions may impact performance and resource usage.</t>
        </li>
        <li>
          <t>Implementations MUST ensure that all subschema references resolve within the same document or trusted sources to prevent external schema injection.</t>
        </li>
      </ul>
    </section>
    <section anchor="iana-considerations">
      <name>IANA Considerations</name>
      <t>This document does not require any IANA actions.</t>
    </section>
  </middle>
  <back>
    <references anchor="sec-normative-references">
      <name>Normative References</name>
      <reference anchor="RFC2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author fullname="S. Bradner" initials="S." surname="Bradner"/>
          <date month="March" year="1997"/>
          <abstract>
            <t>In many standards track documents several words are used to signify the requirements in the specification. These words are often capitalized. This document defines these words as they should be interpreted in IETF documents. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
        <seriesInfo name="DOI" value="10.17487/RFC2119"/>
      </reference>
      <reference anchor="RFC4646">
        <front>
          <title>Tags for Identifying Languages</title>
          <author fullname="A. Phillips" initials="A." surname="Phillips"/>
          <author fullname="M. Davis" initials="M." surname="Davis"/>
          <date month="September" year="2006"/>
          <abstract>
            <t>This document describes the structure, content, construction, and semantics of language tags for use in cases where it is desirable to indicate the language used in an information object. It also describes how to register values for use in language tags and the creation of user-defined extensions for private interchange. This document, in combination with RFC 4647, replaces RFC 3066, which replaced RFC 1766. This document specifies an Internet Best Current Practices for the Internet Community, and requests discussion and suggestions for improvements.</t>
          </abstract>
        </front>
        <seriesInfo name="RFC" value="4646"/>
        <seriesInfo name="DOI" value="10.17487/RFC4646"/>
      </reference>
      <reference anchor="RFC8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author fullname="B. Leiba" initials="B." surname="Leiba"/>
          <date month="May" year="2017"/>
          <abstract>
            <t>RFC 2119 specifies common key words that may be used in protocol specifications. This document aims to reduce the ambiguity by clarifying that only UPPERCASE usage of the key words have the defined special meanings.</t>
          </abstract>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="8174"/>
        <seriesInfo name="DOI" value="10.17487/RFC8174"/>
      </reference>
      <reference anchor="JSTRUCT-CORE" target="https://json-structure.github.io/core/draft-vasters-json-structure-core.html">
        <front>
          <title>JSON Structure Core</title>
          <author fullname="Clemens Vasters">
            <organization/>
          </author>
          <date>n.d.</date>
        </front>
      </reference>
    </references>
    <?line 356?>

<section numbered="false" anchor="acknowledgments">
      <name>Acknowledgments</name>
      <t>TODO acknowledge.</t>
    </section>
  </back>
  <!-- ##markdown-source:
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
qPz3I8bEEbE4eXqC9XEmLPcfFqiEHzEoAAA=

-->

</rfc>
