understanding-json-schema icon indicating copy to clipboard operation
understanding-json-schema copied to clipboard

cookbook page, with solutions to frequently asked questions

Open karenetheridge opened this issue 3 years ago • 10 comments

I am opening this as an issue rather than a PR to start, in order to gather feedback and additional content.

There are a number of questions that are asked frequently on stackoverflow, slack, github issues etc that come down to "how do I express this pattern in JSON Schema?" Here are a few of them, and I'm sure we can come up with more:

  • "evaluate this subschema but do not collect annotations" (not so common but it has come up a few times, and it is weird enough that it deserves a mention IMO):
    { not: { not: { ... subschema ... } } }

  • add descriptions to a bunch of enum values, e.g. for form generation:

  {
    anyOf: [
      {
        "description": "...",
        "const": "value0",
      },
      {
        "description": "...",
        "const": "value1",
      },
      ...
    ]
  }
  • simulate OpenAPI's "discriminator":
  {
    allOf: [
      {
        if: {
          type: object,
          required: [ "foo" ],
          properties: { foo: { const: "value0" } }
        },
        then: {
          .. subschema when data has foo: value0
        }
      },
      {
        if: {
          type: object,
          required: [ "foo" ],
          properties: { foo: { const: "value1" } }
        },
        then: {
          .. subschema when data has foo: value1
        }
      },
      {
        if: {
          type: object,
          required: [ "bar" ],
          properties: { bar: { const: "value0" } }
        },
        then: {
          .. subschema when data has bar: value0
        }
      },
      ...
    ],
}

karenetheridge avatar Apr 11 '21 23:04 karenetheridge

I love this. I've been talking with @Relequestual about improving JSON Schema's documentation and content like this is part of the long term plan. Ideally, I'd like to start with minimal updates to Understanding JSON Schema to bring it up to the latest draft. Then get a new website designed and built and port UJS content over. Then do more in-depth documentation plus cookbook, tutorials, and even blogs. Of course that's all very ambitious, so it's great that you're getting the cookbook content rolling.

I'm bookmarking this issue to add to when things occur to me.

jdesrosiers avatar Apr 12 '21 17:04 jdesrosiers

The "simulate OpenAPI's discriminator" example should include required in all the ifs. We can also include an example that uses this pattern with a default alternative (as described at https://json-schema.org/understanding-json-schema/reference/conditionals.html)

jdesrosiers avatar Apr 12 '21 17:04 jdesrosiers

Cookbook idea: contains for draft-04

Equivalent of "contains": { ... schema ... }

"not": {
  "items": {
    "not": { ... schema ... }
  }
}

Full example

{
  "type": "array",
  "allOf": [{ "$ref": "/#definitions/contains-foo" }],

  "definitions": {
    "contains-foo": {
      "not": {
        "items": {
          "not": { "$ref": "#/definitions/foo" }
        }
      }
    },
    "foo": { "const": "foo" }
  }
}

jdesrosiers avatar Apr 12 '21 17:04 jdesrosiers

Cookbook idea: contains for object values

Pattern

"not": {
  "additionalItems": {
    "not": { ... schema ... }
  }
}

Full example

{
  "type": "object",
  "allOf": [{ "$ref": "/#definitions/contains-foo" }],

  "definitions": {
    "contains-foo": {
      "not": {
        "additionalItems": {
          "not": { "$ref": "#/definitions/foo" }
        }
      }
    },
    "foo": { "const": "foo" }
  }
}

jdesrosiers avatar Apr 12 '21 17:04 jdesrosiers

Cookbook idea: The "Implication Pattern" (if/then for draft-04/6)

Pattern: Equivalent to "if": { ... if schema ... }, "then": { ... then schema ... }

"anyOf": [
  { "not": { ... if schema ... } },
  { ... then schema ... }
]

Full Example

{
  "type": "object",
  "properties": {
    "foo": { "type": "string" },
    "bar": { "type": "number" }
  },
  "allOf": [{ "$ref": "foo-bar-implies-bar-is-required" }],

  "definitions": {
    "foo-bar-implies-bar-is-required": {
      "anyOf": [
        {
          "not": {
            "properties": {
              "foo": { "const": "bar" }
            },
            "required": ["foo"]
          }
        },
        { "required": ["bar"] }
      ]
    }
  }
}

jdesrosiers avatar Apr 12 '21 18:04 jdesrosiers

require propertyA OR propertyB, but not both:

{
  "type": "object",
  "properties": {
    "propertyA": { ... },
    "propertyB": { ... }
  },
  "oneOf": [
    {
      "required": ["propertyA"],
      "not": { "required": ["propertyB"] }
    },
    {
      "required": ["propertyB"],
      "not": { "required": ["propertyA"] }
    }
  ]
}

karenetheridge avatar Apr 14 '21 01:04 karenetheridge

The nots are unnecessary in the previous example because it uses oneOf.

jdesrosiers avatar Apr 14 '21 17:04 jdesrosiers

Cookbook idea: Multiple fields required as a unit.

"foo" and "bar" must both be present or neither

{
  "dependentRequired": {
    "foo": ["bar"],
    "bar": ["foo"]
  }
}

"foo", "bar", "baz", and "quux" must all be present or none at all

{
  "dependentRequired": {
    "foo": ["bar"],
    "bar": ["baz"],
    "baz": ["quux"],
    "quux": ["foo"]
  }
}

jdesrosiers avatar Apr 15 '21 20:04 jdesrosiers

Cookbook idea: Multiple fields required as a unit.

Cute! a little confusing though! I've done this in the past:

{
  "type": "object",
  "oneOf": [
    { "required": ["foo","bar","baz","quux"] },
    {
      "properties": {
        "foo": false,
        "bar": false,
        "baz": false,
        "quux": false
      }
    }
  ]
}

karenetheridge avatar Apr 16 '21 01:04 karenetheridge

How do I validate deep nested data when I don't know where it is in the structure?

https://stackoverflow.com/a/67315446/89211

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "definitions": {
    "grandParentToChild": {
        "properties": {
          "grandParent": {
            "properties": {
              "parent": {
                "properties": {
                  "child": {
                    "properties": {
                      "name": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
    }
  },
  "allOf": [
    {
      "$ref": "#/definitions/grandParentToChild"
    }
  ],
  "additionalProperties": {
    "$ref": "#"
  },
  "items": {
    "$ref": "#"
  }
}

Relequestual avatar Apr 29 '21 10:04 Relequestual