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

Translate closed allOf -> closed intersection

Open jablko opened this issue 4 years ago • 1 comments

What do you think about translating e.g.

{
  allOf: [
    {
      type: 'object',
      properties: {a: {type: 'string'}, b: {type: 'integer'}},
      additionalProperties: false,
      required: ['a', 'b']
    },
    {type: 'object', properties: {a: {enum: ['x']}}}
  ]
}

->

    export type AllOf = {␊
      a: string;␊
      b: number;␊
    } & {␊
      a?: "x";␊
    };␊

vs.

    export type AllOf = {␊
      a: string;␊
      b: number;␊
    } & {␊
      a?: "x";␊
      [k: string]: unknown;␊
    };␊

(removing [k: string]: unknown; in cases like this)

{ a: "x", b: 1, additional: undefined } is not an instance of the schema but is assignable to the latter type. It's not assignable to the former type (Type '{ a: "x"; b: number; additional: undefined; }' is not assignable to type 'AllOf'. Object literal may only specify known properties, and 'additional' does not exist in type 'AllOf'.).

This PR is a naive implementation. If any allOf children have "additionalProperties": false it removes [k: string]: unknown; from all of the allOf's (anonymous) children.

additionalProperties is handled in parseSchema() which doesn't have access to the parent (the potential allOf context), so this implementation instead uses "additionalProperties": false to indirectly remove [k: string]: unknown; from children of a "closed" allOf.

TypeScript intersections don't quite line up with JSON Schema allOf ({ "allOf": [<closed>, <open>] } is closed whereas <closed> & <open> is open) but I think this change makes the type slightly more faithful to the schema.

This is useful if you want to extract the common parts of mostly similar, closed schemas. Currently if you do { "allOf": [{ "$ref": "#/components/schemas/common" }, { "type": "object", "properties": { "a": { "enum": ["x"] } } }] }, where #/components/schemas/common has "additionalProperties": false, the type will nevertheless be open, but you can't add "additionalProperties": false to the second allOf item because that will preclude the common properties. The solution proposed here is to make the intersection more faithful to the schema in cases like this.

jablko avatar Feb 22 '21 22:02 jablko

Hey, thanks for the contribution! Would you mind opening an issue first, to explain the motivation for this change a bit more? Also, tests are a must.

bcherny avatar Mar 28 '21 23:03 bcherny