bcherny/json-schema-to-typescript

Identical properties with same `title` generate multiple types

Open

#510 opened on Feb 14, 2023

View on GitHub
 (1 comment) (1 reaction) (0 assignees)TypeScript (449 forks)github user discovery
enhancementgood first issuehelp wanted

Repository metrics

Stars
 (3,302 stars)
PR merge metrics
 (No merged PRs in 30d)

Description

Example: A rectangle schema with two corners. Both corners are the same type and most importantly share a title.

I know this isn't an ideal schema (should ref in the points), but sometimes you just get bad data from somewhere.

import { compile } from 'json-schema-to-typescript';
import * as fs from 'node:fs';

const schema = {
    type: "object",
    title: "rectangle",
    properties: {
        p1: {
            type: "object",
            title: "point",
            properties: {
                x: "number",
                y: "number"
            },
            required: ["x", "y"]
        },
        p2: {
            type: "object",
            title: "point",
            properties: {
                x: "number",
                y: "number"
            },
            required: ["x", "y"]
        }
    },
    required: ["p1", "p2"]
}

fs.writeFileSync("dupe-example.ts", await compile(schema, undefined, { additionalProperties: false, bannerComment: "" }))

this turns into

export interface Rectangle {
  p1: Point;
  p2: Point1;
}
export interface Point {
  x: "number";
  y: "number";
}
export interface Point1 {
  x: "number";
  y: "number";
}

which is correct, I guess? after all, I'm putting garbage in and get garbage out. I'd prefer to get something like

export interface Rectangle {
  p1: Point;
  p2: Point;
}
export interface Point {
  x: "number";
  y: "number";
}

but I'm not sure if that's in the scope of this project. I saw #456, but as far as I understand it, that only allows changing the interface name before1 the incrementing number is appended, not after (so the same as just setting the title).

Ideally, I'd want a way to hard override the generated interface name. "Trust me, this is a Point, don't complain about duplicates"2. As a more complex alternative, this package could "detect" identical properties (with or without matching titles) and reuse the interfacces generated from those. Again, this might be out of scope - we're starting to shift from parse+generate into ✨ magic ✨

Any advice? I'd also be fine with a hacky workaround, can't be worse than regexing through my .ts file3 :^)

Footnotes

  1. see https://github.com/bcherny/json-schema-to-typescript/pull/456/files#diff-abe2818e9c29aadf84401cb7ef8286c7cbce9253eba09f6fa52e22ca4e57b17eR303-R305 - generateName is called after the override and adds a counter

  2. I'm already committing a kind of crime here: I take in lots of schemas and compile them individually, all into one file. I run ts-dedupe on that file which gets rid of interfaces with the same name. This helps a lot when multiple schemas define a "title": "point" somewhere, but can't duplicates within a schema by itself. ts-dedupe only compares interface names, not their contents/definitions.

  3. As a final note: I don't have to have neat types, since I can just create my own Point interface and use that everywhere. After all, typescript will let identical types from different definitions "fit onto" each other. As long as nobody looks at the schemas' type definitions, the 10 different PointXX interfaces aren't noticable

Contributor guide