vega / ts-json-schema-generator

Generate JSON schema from your Typescript sources
MIT License
1.43k stars 190 forks source link

Incorrect output on Distributive Conditional Types #1202

Open horacio-penya opened 2 years ago

horacio-penya commented 2 years ago

Input:

type Expand<K> = K extends string ? { id: K } : never;
export type SimpleEduction = Expand<"boolean" | "custom">;
export type SimpleEduction2 =
  | {id: "boolean";}
  | {id: "custom";};

gets:

    "SimpleEduction": {
      "additionalProperties": false,
      "properties": {
        "id": {
          "enum": [
            "boolean",
            "custom"
          ],
          "type": "string"
        }
      },
      "required": [
        "id"
      ],
      "type": "object"
    }

As if SimpleEduction = {id: "boolean" | "custom"}

The correct result should be the same as SimpleEduction2:

{
  "$ref": "#/definitions/SimpleEduction2",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "SimpleEduction2": {
      "anyOf": [
        {
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "boolean",
              "type": "string"
            }
          },
          "required": [
            "id"
          ],
          "type": "object"
        },
        {
          "additionalProperties": false,
          "properties": {
            "id": {
              "const": "custom",
              "type": "string"
            }
          },
          "required": [
            "id"
          ],
          "type": "object"
        }
      ]
    }
  }
}

Reference: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

They seem to be equivalent but the difference affects how deriving types from SimpleEduction work.

domoritz commented 2 years ago

What's an example of a json that is valid with one but not the other?

horacio-penya commented 2 years ago

At that point they are equivalent, the problem appears when you derive types from that one.

The full example is:

type BooleanEduction = { id: "boolean" };
type CustomEduction = { id: "custom"; pattern: string };

type StandardizedEduction = BooleanEduction | CustomEduction;

type Expand<K> = K extends string ? { id: K } : never;
// same as SimpleEduction2, replaced by {id: "boolean" | "custom"} by ts-json-schema-generator
type SimpleEduction = Expand<"boolean" | "custom">; 
type SimpleEduction2 = { id: "boolean" } | { id: "custom" };

type ValidEduction<T> = T extends StandardizedEduction ? T : never;
export type SimpleEductionType = ValidEduction<SimpleEduction>["id"]; // type SimpleEductionType = "boolean"

export type SimpleEduction2Type = ValidEduction<SimpleEduction2>["id"]; // type SimpleEduction2Type = "boolean"

For this code, ts-json-schema-generator returns the right result for SimpleEduction2Type and nothing for SimpleEductionType:

{
  "$ref": "#/definitions/SimpleEduction2Type",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "SimpleEduction2Type": {
      "const": "boolean",
      "type": "string"
    }
  }
}
domoritz commented 2 years ago

I see. Thank you for the example.

Do you plan to help fix this issue? I won't have cycles myself.