diff --git a/README.md b/README.md index d9e50cdb..23b96e6c 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ See [server demo](example) and [browser demo](https://github.com/bcherny/json-sc | declareExternallyReferenced | boolean | `true` | Declare external schemas referenced via `$ref`? | | enableConstEnums | boolean | `true` | Prepend enums with [`const`](https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)? | | inferStringEnumKeysFromValues | boolean | `false` | Create enums from JSON enums with eponymous keys | +| exactOptionalPropertyTypes | boolean | `false` | Append all optional property signatures with `\| undefined` so that they are strictly typed in accordance with TypeScript's [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) option. | | format | boolean | `true` | Format code? Set this to `false` to improve performance. | | ignoreMinAndMaxItems | boolean | `false` | Ignore maxItems and minItems for `array` types, preventing tuples being generated. | | maxItems | number | `20` | Maximum number of unioned tuples to emit when representing bounded-size array types, before falling back to emitting unbounded arrays. Increase this to improve precision of emitted types, decrease it to improve performance, or set it to `-1` to ignore `maxItems`. diff --git a/package-lock.json b/package-lock.json index 8713126e..cce0dbf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "json-schema-to-typescript", - "version": "14.0.5", + "version": "15.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "json-schema-to-typescript", - "version": "14.0.5", + "version": "15.0.0", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.5.5", diff --git a/src/cli.ts b/src/cli.ts index 1206d475..a3f08e6c 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,6 +20,7 @@ main( 'additionalProperties', 'declareExternallyReferenced', 'enableConstEnums', + 'exactOptionalPropertyTypes', 'format', 'ignoreMinAndMaxItems', 'strictIndexSignatures', diff --git a/src/generator.ts b/src/generator.ts index fb8d23b0..aa07477d 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -308,7 +308,8 @@ function generateInterface(ast: TInterface, options: Options): string { escapeKeyName(keyName) + (isRequired ? '' : '?') + ': ' + - type, + type + + (!isRequired && options.exactOptionalPropertyTypes ? ' | undefined' : ''), ) .join('\n') + '\n' + diff --git a/src/index.ts b/src/index.ts index 1aa67be0..c8e3a9b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,6 +52,12 @@ export interface Options { * Create enums from JSON enums with eponymous keys */ inferStringEnumKeysFromValues: boolean + /** + * Append all optional property signatures with `| undefined` so that they are strictly typed in accordance with + * TypeScript's [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes) + * option. + */ + exactOptionalPropertyTypes: boolean /** * Format code? Set this to `false` to improve performance. */ @@ -100,6 +106,7 @@ export const DEFAULT_OPTIONS: Options = { declareExternallyReferenced: true, enableConstEnums: true, inferStringEnumKeysFromValues: false, + exactOptionalPropertyTypes: false, format: true, ignoreMinAndMaxItems: false, maxItems: 20, diff --git a/test/__snapshots__/test/test.ts.md b/test/__snapshots__/test/test.ts.md index da07fbea..511a4e25 100644 --- a/test/__snapshots__/test/test.ts.md +++ b/test/__snapshots__/test/test.ts.md @@ -2326,6 +2326,33 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## options.exactOptionalPropertyTypes.js + +> Expected output to match snapshot for e2e test: options.exactOptionalPropertyTypes.js + + `/* eslint-disable */␊ + /**␊ + * This file was automatically generated by json-schema-to-typescript.␊ + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,␊ + * and run json-schema-to-typescript to regenerate this file.␊ + */␊ + ␊ + export interface ExactOptionalPropertyTypes {␊ + maybe?: string | undefined;␊ + complex?:␊ + | {␊ + maybe?: string | undefined;␊ + [k: string]: Leaf;␊ + }␊ + | undefined;␊ + [k: string]: string;␊ + }␊ + export interface Leaf {␊ + maybe?: string | undefined;␊ + [k: string]: unknown;␊ + }␊ + ` + ## options.format.js > Expected output to match snapshot for e2e test: options.format.js diff --git a/test/__snapshots__/test/test.ts.snap b/test/__snapshots__/test/test.ts.snap index 564bd36c..9d9d7e6a 100644 Binary files a/test/__snapshots__/test/test.ts.snap and b/test/__snapshots__/test/test.ts.snap differ diff --git a/test/e2e/options.exactOptionalPropertyTypes.ts b/test/e2e/options.exactOptionalPropertyTypes.ts new file mode 100644 index 00000000..6d83baf5 --- /dev/null +++ b/test/e2e/options.exactOptionalPropertyTypes.ts @@ -0,0 +1,33 @@ +export const input = { + title: 'ExactOptionalPropertyTypes', + type: 'object', + properties: { + maybe: { + type: 'string', + }, + complex: { + type: 'object', + properties: { + maybe: { + type: 'string', + }, + }, + additionalProperties: { + title: 'Leaf', + type: 'object', + properties: { + maybe: { + type: 'string', + }, + }, + }, + }, + }, + additionalProperties: { + type: 'string', + } +} + +export const options = { + exactOptionalPropertyTypes: true, +}