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

Combination of intersected type and type transform can cause compilation to incorrect object type for non-primitive child properties

Open robcmills opened this issue 4 months ago • 1 comments

The combination of an intersected type and type transform (e.g. any TypeScript Utility Type), can cause compilation to incorrect object type, on any non-primitive (e.g. Array, Tuple) child properties.

type Intersected = {
  /** Array of vertex indices. */
  v: number[];
} & {};

export type Transformed = Required<Intersected>

compiles to incorrect json schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/B",
  "definitions": {
    "Transformed": {
      "type": "object",
      "properties": {
        "v": {
          "type": "object",
          "properties": {},
          "description": "Array of vertex indices.",
        },
      },
      "additionalProperties": false,
    },
  },
}

It appears to be only the combination of the intersected type and the type transform that causes this issue. If you remove either, schema generation is correct:

export type Intersected = {
  /** Array of vertex indices. */
  v: number[];
} & {};

type NonIntersected = {
  /** Array of vertex indices. */
  v: number[];
};

export type Transformed = Required<NonIntersected>

generates correct json schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Intersected": {
      "type": "object",
      "properties": {
        "v": {
          "type": "array",
          "items": {
            "type": "number",
          },
          "description": "Array of vertex indices.",
        },
      },
      "required": [ "v" ],
    },
    "Transformed": {
      "type": "object",
      "properties": {
        "v": {
          "type": "array",
          "items": {
            "type": "number",
          },
          "description": "Array of vertex indices.",
        },
      },
    },
  },
}

Also, if the child property type is a primitive, the issue does not occur:

type Intersected = {
  v: number;
} & {};

export type Transformed = Required<Intersected>

generates correct json schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/Transformed",
  "definitions": {
    "Transformed": {
      "type": "object",
      "properties": {
        "v": {
          "type": "number",
        },
      },
      "additionalProperties": false,
    },
  },
}

Clues

Interestingly, if we remove the jsdoc comment, we get a generated schema that may provide some insight into what is happening:

type Intersected = {
  v: number[]; // no jsdoc comment
} & {};

export type Transformed = Required<Intersected>

compiles to:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/Transformed",
  "definitions": {
    "Transformed": {
      "type": "object",
      "properties": {
        "v": {
          "allOf": [
            {
              "type": "array",
              "items": {
                "type": "number",
              },
            }, {
              "type": "object",
              "properties": {},
            }
          ],
        },
      },
      "additionalProperties": false,
    },
  },
}

It appears to be intersecting the child property with an empty object (instead of the parent).

Repro


const config: Config = {
  ...DEFAULT_CONFIG,
  additionalProperties: true,
  path: "test.ts", // above examples
  skipTypeCheck: true,
  tsconfig: "tsconfig.json", // identical to the one used by the `ts-json-schema-generator` package
};
const schema = createGenerator(config).createSchema(config.type);

robcmills avatar Jun 27 '25 23:06 robcmills