JSON schema imports

Schema combination provides one mechanism for the reuse of schemas, by providing the ability to create a schema that logically extends and overrides one or more other schemas. The import mechanism gives you another way to reuse a schema, by creating a shared schema for common pieces and then importing that schema in order to reference its types.

Schema import and schema combine both provide units of reuse. A code analogy would be that the combine instruction is the equivalent of subclassing a given class while the import instruction is the equivalent of merely referencing that class in arguments or return types. Imports always exist in a separate logical namespace. If A imports B, definitions in A and B that have the same name are still two different types.

By contrast, schema combination exists in a single namespace. That is, if A combines B, definitions in A that have the same name as definitions in B are combined together to produce a single definition. From the point of view of downstream systems, everything ends up merged together anyway. Hence, the choice between the import instruction and the combine instruction really comes down to whether you want definitions with the same name to be merged together or not. When you are logically extending a schema, such as creating a custom schema that extends a base configuration schema, you want the combination behavior. If you are referencing a shared schema authored by another team for another purpose, you probably do not want that behavior. In this case, keep the namespaces separate so that you do not have to worry about name conflicts producing unexpected results.

Import syntax

Imports in JSON schema files are done with the custom x-gw-import property. This property is an object where the keys are alias names and the values are fully-qualified JSON schema file names. References to types in the imported file are prefixed with the name of the alias before the # symbol. For example, the following Contact schema imports an Address schema:
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "x-gw-import" : {
    "address" : "gw.px.ete.address-1.0"
  },
  "definitions": {
    "Contact" : {
      "type" : "object",
      "properties" : {
        "EmailAddress1" : {
          "type" : "string"
        },
        "HomePhone" : {
          "type" : "string"
        },
        "HomePhoneCountry" : {
          "type" : "string",
          "x-gw-type" : "typekey.PhoneCountryCode"
        },
        "AllAddresses" : {
          "type" : "array",
          "items" : {
            "$ref" : "address#/definitions/Address"
          }
        },
        "PrimaryAddress" : {
          "$ref" : "address#/definitions/Address"
        }
      }
    }
  }
}
It is possible to override a schema import by combining schemas. For example, suppose a customer has a custom Address schema that defined extensions. In this case, the customer could define a Contact extension schema that overrides the address alias to point to the Address extension schema:
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "x-gw-combine" : ["gw.px.ete.contact-1.0"],
  "x-gw-import" : {
    "address" : "customer.address-1.0"
  }
}
That override then causes all address references inherited from the base contact schema to resolve to definitions in the customer.address-1.0 schema rather than the gw.px.ete.address-1.0 schema.

Externalizing schemas

JSON schema files are often externalized either by running the genExternalSchemas command-line task or in the context of producing a Swagger schema that imports the JSON schema. When the files are externalized in this way, imports are automatically inlined to create a single schema file. Imports are also treated this way for XSD translation. The intention is to make any given schema self-contained for use by downstream tools. No optimal way exists to define JSON schema file imports in a stable way that all tools can understand.

While import mechanisms technically exist for XSDs, they are not always easy to operate with various XSD toolchains. As a result, imports exist as a tool for schema authors to break down their schemas into reusable chunks. However, schema consumers always see a unified view of the schema.

Imports can lead to naming conflicts. For example, the Contact schema could define its own type inline called User. At the same time, the Address schema could also have a type called User. The REST framework attempts to use unqualified definition names in cases where there are no conflicts. However, the framework adds prefixes to ensure names are unique in cases where there are name conflicts. In this case, the output schema uses both User from the Contact schema itself and address_User, the definition from the Address schema.