bcherny / json-schema-to-typescript

Compile JSON Schema to TypeScript type declarations
https://bcherny.github.io/json-schema-to-typescript-browser/
MIT License
2.9k stars 387 forks source link

`$ref` in `allOf` with depth of 2 is not correctly converted when multi-referenced #597

Closed arnauddrain closed 1 month ago

arnauddrain commented 3 months ago

I'm trying to reproduce an inheritance schema like this:

Thing
 |
Vehicle
 |     \ 
Car     Truck

Using the "allOf" keyword with a schema like this:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "test",
  "definitions": {
    "Thing": {
      "type": "object",
      "properties": {
        "name": { "type": "string" }
      },
      "required": ["name"]
    },
    "Vehicle": {
      "type": "object",
      "allOf": [{ "$ref": "#/definitions/Thing" }],
      "properties": {
        "year": { "type": "integer" }
      },
      "required": ["year"]
    },
    "Car": {
      "type": "object",
      "allOf": [{ "$ref": "#/definitions/Vehicle" }],
      "properties": {
        "numDoors": { "type": "integer" }
      },
      "required": ["numDoors"]
    },
    "Truck": {
      "type": "object",
      "allOf": [{ "$ref": "#/definitions/Vehicle" }],
      "properties": {
        "numAxles": { "type": "integer" }
      },
      "required": ["numAxles"]
    }
  },
  "oneOf": [{ "$ref": "#/definitions/Car" }, { "$ref": "#/definitions/Truck" }]
}

The output looks like this:

export type Test = Car | Truck;
export type Car = Car1 & {
  numDoors: number;
};
export type Car1 = Vehicle; // <--- This is expected
export type Vehicle = Vehicle1 & {
  year: number;
};
export type Vehicle1 = Thing;
export type Truck = Truck1 & {
  numAxles: number;
};
export type Truck1 = Vehicle1; // <--- This is unexpected, should be Vehicle
  name: string;
}

Here, Truck is directly inheriting Thing instead of Vehicle, so its TypeScript definition does not includes the year property. If I exchange the properties of the root oneOf, then the the issue appears in the type definition of Car.

bcherny commented 3 months ago

This does seem like a bug.

In your case, I think you actually want extends, which would fix it for you:

https://borischerny.com/json-schema-to-typescript-browser/#schema=%7B%0A%20%20%22$schema%22:%20%22http://json-schema.org/draft-07/schema#%22,%0A%20%20%22$id%22:%20%22test%22,%0A%20%20%22definitions%22:%20%7B%0A%20%20%20%20%22Thing%22:%20%7B%0A%20%20%20%20%20%20%22type%22:%20%22object%22,%0A%20%20%20%20%20%20%22properties%22:%20%7B%0A%20%20%20%20%20%20%20%20%22name%22:%20%7B%20%22type%22:%20%22string%22%20%7D%0A%20%20%20%20%20%20%7D,%0A%20%20%20%20%20%20%22required%22:%20%5B%22name%22%5D%0A%20%20%20%20%7D,%0A%20%20%20%20%22Vehicle%22:%20%7B%0A%20%20%20%20%20%20%22type%22:%20%22object%22,%0A%20%20%20%20%20%20%22extends%22:%20%7B%20%22$ref%22:%20%22#/definitions/Thing%22%20%7D,%0A%20%20%20%20%20%20%22properties%22:%20%7B%0A%20%20%20%20%20%20%20%20%22year%22:%20%7B%20%22type%22:%20%22integer%22%20%7D%0A%20%20%20%20%20%20%7D,%0A%20%20%20%20%20%20%22required%22:%20%5B%22year%22%5D%0A%20%20%20%20%7D,%0A%20%20%20%20%22Car%22:%20%7B%0A%20%20%20%20%20%20%22type%22:%20%22object%22,%0A%20%20%20%20%20%20%22extends%22:%20%7B%20%22$ref%22:%20%22#/definitions/Vehicle%22%20%7D,%0A%20%20%20%20%20%20%22properties%22:%20%7B%0A%20%20%20%20%20%20%20%20%22numDoors%22:%20%7B%20%22type%22:%20%22integer%22%20%7D%0A%20%20%20%20%20%20%7D,%0A%20%20%20%20%20%20%22required%22:%20%5B%22numDoors%22%5D%0A%20%20%20%20%7D,%0A%20%20%20%20%22Truck%22:%20%7B%0A%20%20%20%20%20%20%22type%22:%20%22object%22,%0A%20%20%20%20%20%20%22extends%22:%20%7B%20%22$ref%22:%20%22#/definitions/Vehicle%22%20%7D,%0A%20%20%20%20%20%20%22properties%22:%20%7B%0A%20%20%20%20%20%20%20%20%22numAxles%22:%20%7B%20%22type%22:%20%22integer%22%20%7D%0A%20%20%20%20%20%20%7D,%0A%20%20%20%20%20%20%22required%22:%20%5B%22numAxles%22%5D%0A%20%20%20%20%7D%0A%20%20%7D,%0A%20%20%22oneOf%22:%20%5B%7B%20%22$ref%22:%20%22#/definitions/Car%22%20%7D,%20%7B%20%22$ref%22:%20%22#/definitions/Truck%22%20%7D%5D%0A%7D

arnauddrain commented 3 months ago

Thank you for your answer @bcherny ! extends will indeed generate the correct typescript classes, but then the validation will be incorrect, because the properties of the "parent" objets won't be required anymore and {"numDoors": 2} will become a valid input: https://jsonschema.dev/s/TdKME

bcherny commented 2 months ago

@arnauddrain extends is an old keyword that has been removed in modern JSON Schema versions. The website you linked doesn't seem to implement it correctly. If your validator supports it, extends will work fine the way I suggested.

See the spec: https://github.com/json-schema/json-schema/wiki/Extends/014e3cd8692250baad70c361dd81f6119ad0f696

bcherny commented 1 month ago

Merged https://github.com/bcherny/json-schema-to-typescript/pull/603

bcherny commented 1 month ago

Fix included in v15.0.0.