json-schema-to-typescript icon indicating copy to clipboard operation
json-schema-to-typescript copied to clipboard

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

Open arnauddrain opened this issue 1 year ago • 3 comments

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.

arnauddrain avatar May 29 '24 17:05 arnauddrain

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

bcherny avatar Jun 02 '24 12:06 bcherny

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

arnauddrain avatar Jun 02 '24 12:06 arnauddrain

@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 avatar Jun 07 '24 20:06 bcherny

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

bcherny avatar Jul 22 '24 10:07 bcherny

Fix included in v15.0.0.

bcherny avatar Jul 22 '24 10:07 bcherny