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

fix: inline purely structural generics

Open alexchexes opened this issue 4 months ago • 0 comments

Partially closes #2233


When a type alias with generic parameters ultimately resolves to a purely structural type (no remaining type-parameters, no public reusable symbol), emitting it as a separate $ref just clutters the schema tree. This PR detects those cases and inlines the resolved structure directly into the parent definition, collapsing long reference chains and trimming unused definitions.


Examples

Case 1 — Mapped/Override helper

import { OverrideProperties } from "./util";

export type Base = {
    foo: string;
    bar: number;
};

export type MyType = OverrideProperties<
    Base,
    {
        bar: string;
    }
>;

Before

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Base": { /* ... */ },
    "Merge<Base,structure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192>": {
      "$ref": "#/definitions/Simplify%3C(alias-1249507601-457-604-1249507601-0-1064%3Cdef-alias-1249507601-129-293-1249507601-0-1064%3Cdef-alias-303496744-44-103-303496744-0-192%3E%2Cdef-alias-1249507601-129-293-1249507601-0-1064%3Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E%3E%26alias-1249507601-457-604-1249507601-0-1064%3Cdef-alias-1249507601-293-457-1249507601-0-1064%3Cdef-alias-303496744-44-103-303496744-0-192%3E%2Cdef-alias-1249507601-293-457-1249507601-0-1064%3Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E%3E)%3E"
    },
    "MyType": {
      "$ref": "#/definitions/OverrideProperties%3CBase%2Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E"
    },
    "OverrideProperties<Base,structure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192>": {
      "$ref": "#/definitions/Merge%3CBase%2Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E"
    },
    "Simplify<(alias-1249507601-457-604-1249507601-0-1064<def-alias-1249507601-129-293-1249507601-0-1064<def-alias-303496744-44-103-303496744-0-192>,def-alias-1249507601-129-293-1249507601-0-1064<structure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192>>&alias-1249507601-457-604-1249507601-0-1064<def-alias-1249507601-293-457-1249507601-0-1064<def-alias-303496744-44-103-303496744-0-192>,def-alias-1249507601-293-457-1249507601-0-1064<structure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192>>)>": {
      "additionalProperties": false,
      "properties": {
        "bar": { "type": "string" },
        "foo": { "type": "string" }
      },
      "required": ["bar", "foo"],
      "type": "object"
    }
  }
}

After

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Base": { /* ... */ },
    "MyType": {
      "additionalProperties": false,
      "properties": {
        "bar": { "type": "string" },
        "foo": { "type": "string" }
      },
      "required": ["bar", "foo"],
      "type": "object"
    }
  }
}

Case 2 — ValueOf on a const object

export const RuntimeObject = {
  FOO: "foo-val",
  BAR: "bar-val",
} as const;

export type ValueOf<T> = T[keyof T];

export type MyType = ValueOf<typeof RuntimeObject>;

Before

{
  "$ref": "#/definitions/MyType",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "MyType": {
      "$ref": "#/definitions/ValueOf%3Cobject-810933776-28-72-810933776-28-81-810933776-12-81-810933776-6-81-810933776-0-82-810933776-0-174%3E"
    },
    "ValueOf<object-810933776-28-72-810933776-28-81-810933776-12-81-810933776-6-81-810933776-0-82-810933776-0-174>": {
      "enum": ["foo-val", "bar-val"],
      "type": "string"
    }
  }
}

After

{
  "$ref": "#/definitions/MyType",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "MyType": {
      "enum": ["foo-val", "bar-val"],
      "type": "string"
    }
  }
}

Inlining these internal generics is safe: the generator already skips emitting them as standalone definitions - even if you'd request type: "OverrideProperties" - so this change only cleans the schema output without dropping any functionality.

alexchexes avatar Jun 24 '25 00:06 alexchexes