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

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

Open jeremyjacob opened this issue 1 year ago • 4 comments

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.

jeremyjacob avatar Jun 23 '24 17:06 jeremyjacob

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

bcherny avatar Jun 24 '24 16:06 bcherny

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
});

jeremyjacob avatar Jun 25 '24 02:06 jeremyjacob

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

bcherny avatar Jun 25 '24 09:06 bcherny

Sorry to be that guy, but is there any chance #564 could get reviewed if the conflicts got resolved?

lishaduck avatar May 23 '25 16:05 lishaduck