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

allOf, oneOf dont correctly generate types #381

Open
italicbold opened this issue Apr 13, 2021 · 8 comments · May be fixed by #571
Open

allOf, oneOf dont correctly generate types #381

italicbold opened this issue Apr 13, 2021 · 8 comments · May be fixed by #571

Comments

@italicbold
Copy link

italicbold commented Apr 13, 2021

The following schema:

        "Test": {
            "type": "object",
            "properties": {
                "prop1": { "type": "string" },
                "prop2": { "type": "string" },
                "prop3": { "type": "string"},
                "prop4": { 
                    "type": "array",
                    "items": {
                        "type": "string"
                    },
                    "minItems": 1
                },
                "prop5": { "type": "string" },
                "prop6": { "type": "string" }
            },
            "allOf": [
                { "required": [ "prop1" ] },
                {
                    "oneOf": [
                        { "required": [ "prop2" ] },
                        { "required": [ "prop3" ] },
                        { "required": [ "prop4" ] },
                        { "required": [ "prop5" ] },
                        { "required": [ "prop6" ] }
                    ]
                }
            ],
            "additionalProperties": false
        }

Generates type:

type Test = {
    [k: string]: unknown;
} & (
    | {
          [k: string]: unknown;
      }
    | {
          [k: string]: unknown;
      }
    | {
          [k: string]: unknown;
      }
    | {
          [k: string]: unknown;
      }
    | {
          [k: string]: unknown;
      }
);

Expected type:

type Test = {
    prop1: string;
} & (
    { prop2: string } |
    { prop3: string } |
    { prop4: string[] } |
    { prop5: string } |
    { prop6: string }
);
@italicbold
Copy link
Author

Similar to #378 ?

Used version 10.1.4

@RickZack
Copy link

RickZack commented Apr 15, 2021

@italicbold the mentioned issue is related to the use of allOf, anyOf with properties, and in that case there was an error in the provided example. Here the problem is related to the processing of requested property when inside oneOf, anyOf. I think it is different.

@john-twigg-ck
Copy link

I'm seeing the same problem. Usage of anyOf causes schema to devolve to [k: string]: unknown;

@FloEdelmann
Copy link

FloEdelmann commented Jun 19, 2021

I found that nesting oneOf/anyOf/allOf causes the issue:

Minimum example schema:

{
  "oneOf": [ // or "allOf" or "anyOf"
    {
      "properties": {
        "speed": { "type": "string" }
      },
      "oneOf": [ // or "allOf" or "anyOf"
        { "required": ["speed"] }
      ]
    }
  ]
}
Expected vs. actual output

Expected output:

export type Demo = {
  speed?: string;
  [k: string]: unknown;
};

Actual output:

export type Demo = {
  [k: string]: unknown;
};
Workaround that yields the expected output
 {
   "oneOf": [ // or "allOf" or "anyOf"
     {
       "properties": {
         "speed": { "type": "string" }
       },
+      "if": true,
+      "then": {
         "oneOf": [ // or "allOf" or "anyOf"
           { "required": ["speed"] }
         ]
+      }
     }
   ]
 }
Example schema with a bit more context
{
  "oneOf": [
    {
      "properties": {
        "type": { "const": "NoFunction" }
      },
      "required": ["type"],
      "additionalProperties": false
    },
    {
      "properties": {
        "type": { "const": "ShutterStrobe" },
        "speed": { "type": "string" },
        "speedStart": { "type": "string" },
        "speedEnd": { "type": "string" }
      },
      "required": ["type"],
      "oneOf": [
        { "required": ["speed"] },
        { "required": ["speedStart"] }
      ],
      "dependencies": {
        "speedStart": ["speedEnd"],
        "speedEnd": ["speedStart"]
      },
      "additionalProperties": false
    }
  ]
}

When removing the inner oneOf/anyOf/allOf, or when not being nested in an outer oneOf/anyOf/allOf, the output is as expected.

@goodoldneon
Copy link

AFAICT, it isn't valid JSON schema to use { required: ["property"] } as an allOf item. JSON schema's docs say:

allOf can not be used to “extend” a schema to add more details to it in the sense of object-oriented inheritance. Instances must independently be valid against “all of” the schemas in the allOf. See the section on Subschema Independence for more information.

(Source)

So there doesn't seem to be a problem with json-schema-to-typescript. As for how to generate the TypeScript interface you want, I think the following code achieves the behavior you're looking for. It's more verbose than you'd like, but IMO it's also easier to understand what's going on in the schema.

JSON Schema

View
{
  "title": "Test",
  "type": "object",
  "allOf": [
    {
      "type": "object",
      "properties": {
        "prop1": { "type": "string" },
        "prop2": { "type": "string" },
        "prop3": { "type": "string" },
        "prop4": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "minItems": 1
        },
        "prop5": { "type": "string" },
        "prop6": { "type": "string" }
      },
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": { "prop1": { "type": "string" } },
      "required": ["prop1"],
      "additionalProperties": false
    },
    {
      "oneOf": [
        {
          "type": "object",
          "properties": { "prop2": { "type": "string" } },
          "required": ["prop2"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": { "prop3": { "type": "string" } },
          "required": ["prop3"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": {
            "prop4": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "minItems": 1
            }
          },
          "required": ["prop4"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": { "prop5": { "type": "string" } },
          "required": ["prop5"],
          "additionalProperties": false
        },
        {
          "type": "object",
          "properties": { "prop6": { "type": "string" } },
          "required": ["prop6"],
          "additionalProperties": false
        }
      ]
    }
  ]
}

Output

export type Test = {
  prop1?: string;
  prop2?: string;
  prop3?: string;
  prop4?: [string, ...string[]];
  prop5?: string;
  prop6?: string;
} & {
  prop1: string;
} & (
    | {
        prop2: string;
      }
    | {
        prop3: string;
      }
    | {
        prop4: [string, ...string[]];
      }
    | {
        prop5: string;
      }
    | {
        prop6: string;
      }
  );

@openreply-dleinhaeuser
Copy link

Reading this and this, I would argue, that { "required": ["foo"] } is a perfectly valid schema, that means "if the value is an object, it must have a property foo". In terms of TypeScript that would be Not<object> | { foo: unknown } except there is no Not and I know of no way to express such a thing in TS.

@ericmorand
Copy link

Any news on this one? Is someone working on it?

@anzev
Copy link

anzev commented Dec 13, 2023

We have encountered a similar issue, using release 13.1.1. This is a minimal schema that produces a wrong TS interface.

{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema",
  "properties": {
    "things": {
      "type": "array",
      "minLength": 1,
      "description": "A list of things",
      "items": {
        "type": "object",
        "required": ["thingProp1", "thingProp2"],
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "propOption1": {
                "type": "string"
              }
            },
            "required": ["propOption1"]
          },
          {
            "type": "object",
            "properties": {
              "propOption2": {
                "type": "number"
              }
            },
            "required": ["propOption2"]
          }
        ],
        "properties": {
          "thingProp1": {
            "type": "string"
          },
          "thingProp2": {
            "type": "string"
          }
        }
      }
    }
  }
}

Output:

export interface Demo {
  /**
   * A list of things
   */
  things?: (
    | {
        propOption1: string;
        [k: string]: unknown;
      }
    | {
        propOption2: number;
        [k: string]: unknown;
      }
  )[];
  [k: string]: unknown;
}

It completely ignores thingProp1 and thingProp2. However, I have noticed that if the object schema is not within an array, e.g.:

{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema",
  "required": ["thingProp1", "thingProp2"],
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "propOption1": {
          "type": "string"
        }
      },
      "required": ["propOption1"]
    },
    {
      "type": "object",
      "properties": {
        "propOption2": {
          "type": "number"
        }
      },
      "required": ["propOption2"]
    }
  ],
  "properties": {
    "thingProp1": {
      "type": "string"
    },
    "thingProp2": {
      "type": "string"
    }
  }
}

In this case, the generated output is as expected:

export type Demo = {
  thingProp1: string;
  thingProp2: string;
  [k: string]: unknown;
} & (
  | {
      propOption1: string;
      [k: string]: unknown;
    }
  | {
      propOption2: number;
      [k: string]: unknown;
    }
);

I'll try to take a look at this issue myself, so any pointers where to start looking are very welcome.

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