ts-json-schema-generator icon indicating copy to clipboard operation
ts-json-schema-generator copied to clipboard

Way to define `not` in combination with `discriminator`

Open GKersten opened this issue 1 year ago • 1 comments

Trying to make my way around using this library, so if I missed something please let me know.

I have taken some inspiration from this test: https://github.com/vega/ts-json-schema-generator/blob/next/test/valid-data/discriminator/main.ts

I want to extend it, so that my root JSON also allows for any other user defined JSON, but as soon as a "type" is defined it needs to be strict and validate with a definition using the discriminator.

So some examples:

// should fail: dog is not an "animal_type"
{
  "animal_type": "dog",
}

// should fail: bird is missing "animal_type"
{
  "animal_type": "can_fly",
}

// should succeed
{
  "animal_type": "bird",
  "can_fly": true
}

// should succeed: "animal_type" is not specified, so allow any object
{
  "id": 123,
}

This is what I tried:

export interface Fish {
  animal_type: 'fish';
  found_in: 'ocean' | 'river';
}

export interface Bird {
  animal_type: 'bird';
  can_fly: boolean;
}

/**
 * @discriminator animal_type
 */
export type Animal = Bird | Fish;

export type Root = Animal | any;

Output:

{
  "$ref": "#/definitions/Root",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Animal": {
      "allOf": [
        {
          "if": {
            "properties": {
              "animal_type": {
                "const": "bird",
                "type": "string"
              }
            }
          },
          "then": {
            "$ref": "#/definitions/Bird"
          }
        },
        {
          "if": {
            "properties": {
              "animal_type": {
                "const": "fish",
                "type": "string"
              }
            }
          },
          "then": {
            "$ref": "#/definitions/Fish"
          }
        }
      ],
      "properties": {
        "animal_type": {
          "enum": [
            "bird",
            "fish"
          ]
        }
      },
      "required": [
        "animal_type"
      ],
      "type": "object"
    },
    "Bird": {
      "additionalProperties": false,
      "properties": {
        "animal_type": {
          "const": "bird",
          "type": "string"
        },
        "can_fly": {
          "type": "boolean"
        }
      },
      "required": [
        "animal_type",
        "can_fly"
      ],
      "type": "object"
    },
    "Fish": {
      "additionalProperties": false,
      "properties": {
        "animal_type": {
          "const": "fish",
          "type": "string"
        },
        "found_in": {
          "enum": [
            "ocean",
            "river"
          ],
          "type": "string"
        }
      },
      "required": [
        "animal_type",
        "found_in"
      ],
      "type": "object"
    },
    "Root": {
      "anyOf": [
        {
          "$ref": "#/definitions/Animal"
        },
        {}
      ]
    }
  }
}

Now with this output schema, actually everything becomes valid, because there is always a fallback to any.


What I think I need to add is a not keyword. To make sure the any type does not include animal_type. See: https://json-schema.org/understanding-json-schema/reference/combining#not

Adjusting the above output to the following, seems to result in the behaviour I am looking for:

...
"Root": {
  "anyOf": [
    {
      "$ref": "#/definitions/Animal"
    },
    {
      "not": {
        "required": ["animal_type"]
      }
    }
  ]
}
...

Is there any way to achieve this currently? I still have to figure out if the custom formatting / parsing is a way to achieve this?


Alternatively I could suggest something like this could result in the desired output, but will probably require more research:

/**
 * @not animal_type
 */
export type AnyObject = Record<string, any>;

export type Root = Animal | AnyObject;

btw, thanks for the great work on the library, it looks really promising. Still checking if it will support some more complex scenarios we might run into. Using this tool will be great because it will take away the pain to maintain a really large JSON Schema manually!

GKersten avatar Aug 21 '24 11:08 GKersten