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

Option for adding `undefined` to optional union for optional properties. #604

Open jeremyjacob opened 2 months ago

jeremyjacob commented 2 months ago

In TypeScript, when exactOptionalPropertyTypes is set to true, properties that are optional?: but not do not include | undefined in their union can not be assigned a value of undefined.

For generating code that will be consumed by others, we may not want to force them to turn off exactOptionalPropertyTypes. For that please consider an option to include undefined in generated type unions of non-required properties.

bcherny commented 2 months ago

Could you share an example of a JSON schema and the output it produces today, and how exactly that might cause issues for users?

jeremyjacob commented 2 months ago

No problem. Given the schema,

{
    "title": "Example Schema",
    "type": "object",
    "properties": {
        "firstName": {
            "type": "string"
        },
        "age": {
            "type": "integer",
            "minimum": 0
        }
    },
    "additionalProperties": false,
    "required": ["firstName"]
}

the library currently produces the following output:

export interface ExampleSchema {
  firstName: string;
  age?: number;
}

Let's say we have a function,

function testFunction(data: ExampleSchema) { /* ... */ }

Calling that function with age set to undefined raises a TypeScript error when the TS compiler option exactOptionalPropertyTypes is true:

testFunction({
    firstName: "John",
    age: undefined
});

/*
Argument of type '{ firstName: string; age: undefined; }' is not assignable to parameter of type 'ExampleSchema' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
  Types of property 'age' are incompatible.
    Type 'undefined' is not assignable to type 'number'.
*/

The solution in the above is to simply omit the property altogether, but if the value of age is coming from another possibly undefined variable, things get messier:

const age: number | undefined = undefined;
testFunction({
    firstName: "John",
    ...(age ? { age: age } : {})
});

Our code may internally equate the non-existence of an optional type with the existence a value of undefined. This seems to be common behavior in many libraries. If there does need to be a distinction, like with updating a record, using null seems to be the most widely implemented practice.

Thusly I propose a new option in json-schema-to-typescript to address this use case. Possible names include undefinedOptionals and undefinedOptionalProperties.

The following would be the output of the above schema with this flag enabled:

export interface ExampleSchema {
  firstName: string;
  age?: number | undefined;
}
testFunction({
    firstName: "John",
    age: undefined // Ok
});
bcherny commented 2 months ago

Gotcha. That sounds reasonable to me. I think the more explicit undefinedOptionalProperties is a bit nicer. PRs welcome.