Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Way to define not in combination with discriminator #2046

Open
GKersten opened this issue Aug 21, 2024 · 1 comment
Open

Way to define not in combination with discriminator #2046

GKersten opened this issue Aug 21, 2024 · 1 comment

Comments

@GKersten
Copy link

GKersten commented Aug 21, 2024

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
Copy link
Author

For now, was able to work-around it with the following config:


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

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

export interface AnyObject {
  animal_type: 'user_data';
  [key: string]: any;
}

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

But this now requires me to always add a animal_type property, and set it to user_data to allow any other json. Would still like to know if there is a better alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants