glideapps / quicktype

Generate types and converters from JSON, Schema, and GraphQL

Home Page:https://app.quicktype.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unexpected result for TypeScript Interfaces

Janis-Hahn opened this issue · comments

We are changing from JSON Type Definition to JSON Schema and are now using Quicktype to convert the JSON Schemas to TypeScript Types.
For the most part, Quicktype is doing its job very well, but aparently it can't convert Discriminators, or the JSON Schema equivalent allOf with if-then. It seems to just ignore the allOf...
Did we do anything wrong?

The bash command we use to convert JSON Schema to TypeScript:

quicktype -o ./src/typings.ts --just-types --acronym-style camel --src-lang schema

Expected Result

export type CreateCustomerCommandPayloadV1 = CreateCustomerCommandPayloadV1Person | CreateCustomerCommandPayloadV1Company;

export interface CreateCustomerCommandPayloadV1Person {
    type: CreateCustomerCommandPayloadV1Type.Person;
    customerKey: string
    firstName: string
    lastName: string
}
export interface CreateCustomerCommandPayloadV1Company {
  type: CreateCustomerCommandPayloadV1Type.Company;
  customerKey: string
  companyName: string
}

export enum CreateCustomerCommandPayloadV1Type {
    Company = "COMPANY",
    Person = "PERSON",
}

Actual Result

export interface CreateCustomerCommandPayloadV1 {
    type: CreateCustomerCommandPayloadV1Type;
}

export enum CreateCustomerCommandPayloadV1Type {
    Company = "COMPANY",
    Person = "PERSON",
}

Our JSON Schema File:

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "metadata": {
    "description": "Command: Creates a new customer (Types: COMPANY | PERSON)",
    "subject": "commands.crm.customers.createCustomer",
    "authenticationRequired": true
  },
  "type": "object",
  "additionalProperties": false,
  "$id": "CreateCustomerCommandPayloadV1",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "COMPANY",
        "PERSON"
      ]
    },
    "customerKey": {
      "type": "string"
    }
  },
  "required": [
    "type"
  ],
  "$comment": "discriminator",
  "allOf": [
    {
      "if": {
        "properties": {
          "type": {
            "const": "COMPANY"
          }
        }
      },
      "then": {
        "properties": {
          "companyName": {
            "type": "string"
          }
        },
        "required": [
          "companyName"
        ]
      }
    },
    {
      "if": {
        "properties": {
          "type": {
            "const": "PERSON"
          }
        }
      },
      "then": {
        "properties": {
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
        },
        "type": "object",
        "additionalProperties": false,
        "title": "CreateCustomerCommandPayloadV1Person",
        "required": [
          "firstName",
          "lastName"
        ]
      }
    }
  ]
}

It looks to me like you need to use oneOf (must match one and only one of the provided schemas) instead of allOf:

{
  "$schema": "http://json-schema.org/draft-07/schema",
  "metadata": {
    "description": "Command: Creates a new customer (Types: COMPANY | PERSON)",
    "subject": "commands.crm.customers.createCustomer",
    "authenticationRequired": true
  },
  "$id": "CreateCustomerCommandPayloadV1",
  "oneOf": [
    {
      "type": "object",
      "additionalProperties": false,
      "title": "CreateCustomerCommandPayloadV1Company",
      "properties": {
        "type": {
          "type": "string",
          "const": "COMPANY"
        },
        "customerKey": {
          "type": "string"
        },
        "companyName": {
          "type": "string"
        }
      },
      "required": [
        "type",
        "companyName"
      ]
    },
    {
      "type": "object",
      "additionalProperties": false,
      "title": "CreateCustomerCommandPayloadV1Person",
      "properties": {
        "type": {
          "type": "string",
          "const": "PERSON"
        },
        "customerKey": {
          "type": "string"
        },
        "firstName": {
          "type": "string"
        },
        "lastName": {
          "type": "string"
        }
      },
      "required": [
          "type",
          "firstName",
          "lastName"
        ]
     }
  ]
}

The schemas inside the oneOf could themselves be composed via allOf to reduce the duplication -- mainly just illustrating that you can/should use oneOf for a conditional case (and its worth reading about the difference between oneOf, allOf and anyOf (anyOf would probably give you a single object with optional properties for everything from both subschemas, where oneOf should give you two separate objects.

Not familiar with that metadata element. Usually I'd expect title/description field in the top-level of the schema.
https://json-schema.org/draft-06/json-schema-validation#rfc.section.7

(n.b. Not associated with the project but happen to be working on something similar)