SchemaVela

Technical notes

Schemas

OpenAPI Schema vs JSON Schema

How an OpenAPI 3.1 Schema Object differs from a JSON Schema document, including dialect, added keywords, and the two definitions of integer.

A printed library card-catalog page, a list of records rather than one record.
Image from the Internet Archive Book Images, no known restrictions, via Wikimedia Commons. The photograph is a real object or place. It is not a trace of a message from this note.

An OpenAPI Schema Object and a JSON Schema document can describe the same JSON value and still be different files. OpenAPI 3.1.0 bases its data types on JSON Schema Draft 2020-12 and says the Schema Object is a superset of that draft. The superset is the comparison: JSON Schema keywords apply, and an OpenAPI document adds a dialect, a short list of extended keywords, and an HTTP API around the schema. This note is about which file you are holding. What an API schema constrains is the field layer inside either file. What a validator does with format is the check, and it is a separate question from the file type.

Which file you are holding

An OpenAPI document describes an HTTP API. Its root is an OpenAPI Object. The openapi field is required. It is the version of the OpenAPI Specification the document uses, and the specification says it is not related to info.version. The info object is required. The specification recommends the file names openapi.json and openapi.yaml. It does not say a parser must reject another name.

Message shapes inside that document are Schema Objects. components.schemas is a map of reusable Schema Objects. Paths, webhooks, servers, and security requirements are fields of the API document. They are not JSON Schema keywords, and they do not become keywords by sitting in the same file as a schema.

A JSON Schema document describes a JSON instance. Draft 2020-12 says every schema has a meta-schema, which can be declared with the $schema keyword. That keyword is both a dialect identifier and the identifier of a resource which is itself a schema: the schema that describes which schemas are valid for that dialect. The value must be a URI. The keyword should be used on the document root schema. If it is absent from the document root, the specification says the resulting behavior is implementation-defined. That sentence belongs to JSON Schema. OpenAPI states a different default for Schema Objects that live inside an OpenAPI document, and the next section is that default.

The illustration below is not a captured API. It shows the two roots. The $schema value in the first object is the value Draft 2020-12 uses in its own output examples.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "sku": { "type": "string" }
  }
}
{
  "openapi": "3.1.0",
  "info": { "title": "Illustration", "version": "1" },
  "components": {
    "schemas": {
      "Item": {
        "type": "object",
        "properties": {
          "sku": { "type": "string" }
        }
      }
    }
  }
}

Illustration, not a captured message. The first root is a schema. The second root is an OpenAPI document that contains a schema. info.version here is a placeholder string, not a compatibility rule.

Putting the first object under components.schemas reuses the schema. It does not turn the bare file into an API description. How a version records a compatibility promise is where info.version belongs. The openapi field does not answer it.

What the superset commits you to

In the OpenAPI 3.1.0 data-types section, data types are based on the types supported by JSON Schema Draft 2020-12. The Schema Object is described there, and again in its own section, as a superset of that draft. Unless stated otherwise, Schema Object properties follow JSON Schema and do not add semantics. Where JSON Schema says behavior is defined by the application, for example for annotations, OpenAPI also leaves that behavior to the application that consumes the OpenAPI document.

The dialect is more specific than the word “superset.” OpenAPI says the Schema Object dialect requires the OAS base vocabulary, in addition to the vocabularies in the JSON Schema Draft 2020-12 general-purpose meta-schema. The dialect for this version of the specification is identified by https://spec.openapis.org/oas/3.1/dialect/base. The specification calls that URI the OAS dialect schema id.

Which dialect a tool must apply depends on where the schema sits.

$schema may appear on any root Schema Object. If it is present, OpenAPI says it must be used to determine the dialect. That is how a Schema Object can comply with a JSON Schema draft other than the default Draft 2020-12. Tooling must support the OAS dialect schema id. It may support other values of $schema. Support for a second dialect is allowed, not required.

The OpenAPI Object may set jsonSchemaDialect. That value is the default $schema for Schema Objects contained in the document, and it must be a URI. If jsonSchemaDialect is not set, the OAS dialect schema id must be used for those Schema Objects. A $schema on a Schema Object overrides that default.

A schema outside the OpenAPI document follows different rules. When the reference target is not an OAS document, OpenAPI’s example is a bare JSON Schema resource, and $schema in that resource must follow JSON Schema rules. A bare file with no $schema does not pick up the OAS dialect id because an OpenAPI document points at it. A validator on that file and a validator on a Schema Object inside the document are not promised the same default. The validation note already records that under schema drift: two files that share a name can still be different dialects.

Keywords the OpenAPI dialect adds

OpenAPI names two JSON Schema properties whose definitions it extends:

  • description may use CommonMark.
  • format keeps the formats JSON Schema defines, and OpenAPI adds predefined formats of its own. The data-types table lists int32 and int64 on integer, float and double on number, and password on string. The comment on password is a hint to user interfaces to obscure input.

The OAS base vocabulary, in the Schema Object fixed fields, is four keywords:

  • discriminator adds support for polymorphism. It names a property used to differentiate schemas that may satisfy the payload.
  • xml may be used only on property schemas. It has no effect on root schemas.
  • externalDocs points at further documentation for the schema.
  • example is a free-form instance. OpenAPI deprecates it in favor of the JSON Schema examples keyword, discourages its use, and says later versions of the specification may remove it.

The Schema Object also supports keywords from other vocabularies, or entirely arbitrary properties. It may be extended with specification extensions, and inside this object those additional properties may omit the x- prefix.

OpenAPI lists discriminator, xml, and externalDocs in the OAS base vocabulary. They are not in the pair of JSON Schema properties it says it extended, description and format. Tooling must support the OAS dialect. This desk treats a tool that drops an unknown keyword and still reports the instance as valid as silence, not as acceptance. Whether format rejects a bad string is the same kind of silence unless the dialect asserts formats. That case stays in the validation note.

JSON Schema’s type is a string or an array of unique strings. OpenAPI 3.1.0 lists description and format as the properties it extends, and does not list type. This desk reads that list as leaving the JSON Schema definition of type in place, including the array form. The reading classifies the published list. It is not a second definition of type.

Two definitions of integer

Both documents define an integer type. The sentences are not the same.

JSON Schema Validation says the string values of type must be one of the six primitive types, null, boolean, object, array, number, or string, or integer, which matches any number with a zero fractional part.

OpenAPI 3.1.0 says integer as a type is also supported and is defined as a JSON number without a fraction or exponent part.

This note does not treat the two sentences as one rule, and it does not claim that a given literal passes one and fails the other. If two stacks generate the client and the server, write down which sentence the contract uses. A field labeled only “integer” has not made that choice.

Table 1. Which document answers the question
Question Where it is answered Easy mix-up
Is this file an HTTP API description? An OpenAPI root, with openapi and info Reading a bare schema as an API
Which dialect applies inside an OpenAPI document? $schema on the root Schema Object, otherwise jsonSchemaDialect, otherwise the OAS dialect schema id Assuming Draft 2020-12 from the mention alone
Which dialect applies to a bare external schema? JSON Schema rules for $schema Giving a bare file the OAS default
May description be CommonMark? OpenAPI’s extension of that property Expecting markdown in every renderer
What does integer match? The sentence in the dialect you named Validating with the other sentence

The middle column is a classification used by this desk. The dialect order and the integer sentences are the cited documents. The right-hand column is not a measurement of how often the mix-up happens.

A checklist before you treat the files as the same

Use this when a repository contains both an OpenAPI document and a schema file. It is a review list, not a score.

  1. If the root has openapi and info, read it as an OpenAPI document. If the root is a schema, read it as a JSON Schema resource.
  2. Inside the OpenAPI document, record the dialect actually in force: $schema if the root Schema Object has one, otherwise jsonSchemaDialect, otherwise https://spec.openapis.org/oas/3.1/dialect/base.
  3. Do not copy that default onto a bare external file. If that file has no $schema, JSON Schema says the behavior is implementation-defined. Name the dialect in the file, or name the implementation you tested.
  4. discriminator, xml, externalDocs, and example are OpenAPI vocabulary. Silence from a JSON Schema-only tool is not a passing check.
  5. CommonMark in description, and the formats int32, int64, float, double, and password, are OpenAPI extensions. password is a hint to obscure input. Whether format rejects a value stays in the validation note.
  6. Write the integer sentence you mean, or name the dialect, instead of the word alone.
  7. Status codes, paths, and security stay on the OpenAPI operation. The schema note’s table is the longer list of what a schema leaves open.

Open questions

OpenAPI 3.1.0 requires tooling to support the OAS dialect schema id and allows other $schema values. It does not say that a particular generator does either. A generator that accepts only one draft URI is showing its own limit. Test it against the dialect URI you publish, where you already test validation. This note does not report what any version after 3.1.0 did with the deprecated example keyword.

Sources

  1. OpenAPI Specification 3.1.0 — OpenAPI Initiative
  2. JSON Schema Core, Draft 2020-12, section 8.1.1 ($schema) — JSON Schema
  3. JSON Schema Core, Draft 2020-12, section 8.1 (meta-schemas) — JSON Schema
  4. JSON Schema Core, Draft 2020-12, section 12.4 (output examples) — JSON Schema
  5. JSON Schema Validation, Draft 2020-12, section 6.1.1 (type) — JSON Schema