Suddenly encountered issue when working with TypeScript Interfaces while integrating Quicktype 'allOf'

Our transition from using JSON Type Definition to JSON Schema includes the utilization of Quicktype to convert JSON Schemas into TypeScript Types. While Quicktype has been effective in most cases, it seems to struggle with converting Discriminators and more specifically the JSON Schema equivalent allOf with if-then scenarios, as it appears to disregard the allOf component altogether. Have we made an error in this process?

The command-line code we employ for converting JSON Schema to TypeScript is:

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

Expected Output

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 Output

export interface CreateCustomerCommandPayloadV1 {
    type: CreateCustomerCommandPayloadV1Type;
}

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

Details Regarding 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"
        ]
      }
    }
  ]
}

Answer №1

If you are considering switching your schema to utilize oneOf instead of if, then, here are the outcomes. However, there is still room for improvement.

{
    "$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",
    "$id": "CreateCustomerCommandPayloadV1",
    "oneOf": [
        {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string",
                    "enum": [
                        "PERSON"
                    ]
                },
                "customerKey": {
                    "type": "string"
                },
                "firstName": {
                    "type": "string"
                },
                "lastName": {
                    "type": "string"
                }
            },
            "additionalProperties": false,
            "required": [
                "type",
                "firstName",
                "lastName"
            ]
        },
        {
            "type": "object",
            "properties": {
                "type": {
                    "type": "string",
                    "enum": [
                        "COMPANY"
                    ]
                },
                "companyName": {
                    "type": "string"
                },
                "customerKey": {
                    "type": "string"
                }
            },
            "additionalProperties": false,
            "required": [
                "type",
                "companyName"
            ]
        }
    ]
}
export interface Typings {
    customerKey?: string;
    firstName?:   string;
    lastName?:    string;
    type:         Type;
    companyName?: string;
}

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

quicktype --out typings.ts --just-types  --acronym-style camel --src-lang schema --src schema.json --lang ts

Answer №2

This scenario often leads to confusion and frustration, especially when dealing with the additionalProperties: false and allOf keywords.

Prior to September 2019, many individuals had a misconception about how these keywords should interact. In reality, each schema – the main one and any allOf sub-schemas – are validated separately. Setting additionalProperties: false in the root schema means no additional properties are allowed, irrespective of definitions in other subschemas.

Consider this example: Only specific instances will pass for the additionalProperties, as shown below.

{}
true
false
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "additionalProperties": false,
  "allOf": [
    {"properties": {"name": {"type": "string"}}},
    {"properties": {"age": {"type": "number"}}}
  ]
}

To address this issue from a JSON Schema perspective, modifying the root schema to include the expected properties from allOf sub-schemas is necessary. Assigning a boolean schema of true to each property suffices for validation purposes.

Furthermore, within the context of if, then, redefining the type property with a boolean schema of true becomes essential to denote its validity, particularly if additionalProperties: false is reiterated in the then statement.

Correcting these schema discrepancies does not guarantee support by QuickType. Collaboration with their project maintainers may be needed for further assistance.

{
  "$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"
    },
    "companyName": true,
    "firstName": true,
    "lastName": true
  },
  "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": true
          
        },
        "type": "object",
        "additionalProperties": false,
        "title": "CreateCustomerCommandPayloadV1Person",
        "required": [
          "firstName",
          "lastName"
        ]
        
      }
      
    }
    
  ]
  
}

The outcome generated by QuickType following adjustments to this schema might not match your expectations but illustrates the modifications made.

export interface Typings {
    companyName?: any;
    customerKey?: string;
    firstName ?: any;
    lastName ?: any;
    type : Type;
}

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


 quicktype -o typings.ts --just-types --acronym-style camel -s schema schema.json

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Can you explain the purpose of this TypeScript code snippet? It declares a variable testOptions that can only be assigned one of the values "Undecided," "Yes," or "No," with a default value of "Undecided."

const testOptions: "Undecided" | "Yes" | "No" = "Undecided"; Can you explain the significance of this code snippet in typescript? How would you classify the variable testOptions? Is testOptions considered an array, string, or another d ...

Experiencing the 'invalid_form_data' error while attempting to upload a file to the Slack API via the files.upload method in Angular 8

I am currently working on a project that involves collecting form data, including a file upload. I am trying to implement a feature where the uploaded file is automatically sent to a Slack channel upon submission of the form. Despite following the guidance ...

Is it possible to eliminate the table borders and incorporate different colors for every other row?

Eliminating the table borders and applying color to alternate rows. Check out my code snippet: https://stackblitz.com/angular/dnbermjydavk?file=app%2Ftable-overview-example.ts. ...

Installation and execution of TypeScript jQuery / Bootstrap definition file on a local machine using npm typings: A step-by-step guide

Struggling to set up TypeScript jQuery and Bootstrap definition files in my new project using npm typings. Below are the steps I followed: 1- Open cmd, navigate to my project folder, and enter the following commands: npm install typings --global typings ...

What could be causing the TypeScript type error within this Effector effect subscriber?

Working on a front-end application utilizing React, Typescript, Effector, FetchAPI, and other technologies. Created an Effector effect to delete an item in the backend: export const deleteItemFX = createEffect({ handler: (id: string) => { return ...

Issue with reflect metadata in Next.js edge runtime causing functional problems

Currently, I am utilizing a package in my upcoming 13 app that incorporates reflect metadata. Unfortunately, during the next build process, an error occurs for which I haven't been able to find a solution. ../../eshop-sdk-js/node_modules/reflect-metad ...

Is it possible to validate a template-driven form without using the model-driven approach?

Attempting to validate a template-driven form in Angular without two-way data binding has proved to be challenging. I have successfully implemented validation using [(ngModel)], but running into an error when trying to validate the form without the MODEL p ...

Leveraging the power of the Async pipe within an Angular TypeScript file

Using the async pipe in HTML involves utilizing the syntax "Products$ | async as products". But can we also access these same products in the TypeScript file? Is this possible? ...

Handling type errors with React Typescript MuiAccordion OnChange event handler

I'm a beginner in typescript and seeking advice on defining the type for an event handler. I have a component that utilizes material ui Accordion and triggers the handler from a container. Therefore, I need to specify the type of handleChange in my co ...

What is the process for exporting a class and declaring middleware in TypeScript?

After creating the user class where only the get method is defined, I encountered an issue when using it in middleware. There were no errors during the call to the class, but upon running the code, a "server not found" message appeared. Surprisingly, delet ...

I encountered an issue while trying to implement a custom pipe using the built-in pipe

My custom pipe seems to be functioning well, except for the built-in pipes not working within it. I've imported Pipe and can't figure out what could be causing the issue. Below is the code with the errors: import { Pipe, PipeTransform } from &a ...

An error occurred while attempting to set up Next-auth in the process of developing

In my Next.js app, I have implemented next-auth for authentication. During local development, everything works fine with 'npm install' and 'npm run dev', but when I try to build the project, I encounter this error message: ./node_modul ...

Employing a provider within a different provider and reciprocally intertwining their functions

I'm currently facing an issue with two providers, which I have injected through the constructor. Here's the code for my user-data.ts file: @Injectable() export class UserDataProvider { constructor(private apiService: ApiServiceProvider) { ...

Dealing with Angular 2's Http Map and Subscribe Problem

Looking to parse a JSON file and create a settingsProvider. This is how I am attempting it: import {Http} from "angular2/http"; import {Injectable} from "angular2/core"; @Injectable() export class SettingsProvider{ url: string = ""; constructor ...

Click function for mat-step event handler

Is it feasible to create a click event for the mat-step button? I want to be able to add a (click) event for each mat-step button that triggers a method. Essentially, I am looking to make the mat-step button function like a regular button. You can find mo ...

Utilizing numerical values in useParams - A beginner's guide

Trying to access specific data from my json file using an ID, like "http://localhost:3001/pokemons/3", leads to a 404 error. All the data is visible at http://localhost:3001/pokemons. It seems that useParams doesn't want me to use id as a number - q ...

Creating nested return types: A guide to defining function return types within a Typescript class

My large classes contain functions that return complex objects which I am looking to refactor. class BigClass { ... getReferenceInfo(word: string): { isInReferenceList:boolean, referenceLabels:string[] } { ... } } I am considering somethi ...

Guide to transforming a TaskOption into a TaskEither with fp-ts

I have a method that can locate an item in the database and retrieve a TaskOption: find: (key: SchemaInfo) => TO.TaskOption<Schema> and another method to store it: register: (schema: Schema) => TE.TaskEither<Error, void> Within my regis ...

The test suite encountered an error: Invariant violation occurred because the statement "Buffer.from("") instanceof Uint8Array" was evaluated as false when it should have been

**Error: The condition "Buffer.from("") instanceof Uint8Array" is incorrectly evaluating to false This error indicates a problem with your JavaScript environment. eBuild relies on this specific condition which suggests that your JS environment is not funct ...

When attempting to retrieve information from the API, an error occurred stating that property 'subscribe' is not found in type 'void'

I've attempted to use this code for fetching data from an API. Below is the content of my product.service.ts file: import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { map, Observ ...