From 3632d5d56b652ce9708097ac1c9d7105cb3a8006 Mon Sep 17 00:00:00 2001 From: skarab42 Date: Fri, 26 Aug 2022 09:31:27 +0200 Subject: [PATCH] Add `Jsonify` support for optional keys #424 --- source/jsonify.d.ts | 34 ++++++++++++++++++++++++++++------ test-d/jsonify.ts | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/source/jsonify.d.ts b/source/jsonify.d.ts index 8daa93b79..3b5570c5b 100644 --- a/source/jsonify.d.ts +++ b/source/jsonify.d.ts @@ -1,10 +1,32 @@ import type {JsonPrimitive, JsonValue} from './basic'; +import type {Merge} from './merge'; import type {NegativeInfinity, PositiveInfinity} from './numeric'; import type {TypedArray} from './typed-array'; // Note: The return value has to be `any` and not `unknown` so it can match `void`. type NotJsonable = ((...args: any[]) => any) | undefined | symbol; +// Returns never if the key or property is not jsonable without testing whether the property is required or optional otherwise return the key. +type BaseKeyFilter = Key extends symbol + ? never + : Type[Key] extends symbol + ? never + : [(...args: any[]) => any] extends [Type[Key]] + ? never + : Key; + +// Returns never if the key or property is not jsonable or optional otherwise return the key. +type RequiredKeyFilter = undefined extends Type[Key] + ? never + : BaseKeyFilter; + +// Returns never if the key or property is not jsonable or required otherwise return the key. +type OptionalKeyFilter = undefined extends Type[Key] + ? Type[Key] extends undefined + ? never + : BaseKeyFilter + : never; + /** Transform a type to one that is assignable to the `JsonValue` type. @@ -82,9 +104,9 @@ export type Jsonify = : T extends TypedArray ? Record : T extends any[] ? {[I in keyof T]: T[I] extends NotJsonable ? null : Jsonify} - : {[P in keyof T as P extends symbol ? never - : T[P] extends NotJsonable ? never - : P - ]: Jsonify[P]>} // Recursive call for its children - : never // Otherwise any other non-object is removed - : never; // Otherwise non-JSONable type union was found not empty + : Merge< + {[Key in keyof T as RequiredKeyFilter]: Jsonify}, + {[Key in keyof T as OptionalKeyFilter]?: Jsonify>} + > // Recursive call for its children + : never // Otherwise any other non-object is removed + : never; // Otherwise non-JSONable type union was found not empty diff --git a/test-d/jsonify.ts b/test-d/jsonify.ts index e972a8325..0a8aa183c 100644 --- a/test-d/jsonify.ts +++ b/test-d/jsonify.ts @@ -235,5 +235,41 @@ declare const jsonifiedOptionalTypeUnion: Jsonify; declare const jsonifiedNonOptionalTypeUnion: Jsonify; expectType<{a?: string}>(jsonifiedOptionalPrimitive); -expectType<{a?: never}>(jsonifiedOptionalTypeUnion); -expectType<{a: never}>(jsonifiedNonOptionalTypeUnion); +expectType<{}>(jsonifiedOptionalTypeUnion); +expectType<{a?: string}>(jsonifiedNonOptionalTypeUnion); + +// Test for 'Jsonify support for optional object keys, unserializable object values' #424 +// See https://github.com/sindresorhus/type-fest/issues/424 +type AppData = { + // Should be kept + requiredString: string; + requiredUnion: number | boolean; + + // Should be kept and set to optional + optionalString?: string; + optionalUnion?: number | string; + optionalStringUndefined: string | undefined; + optionalUnionUndefined: number | string | undefined; + + // Should be omitted + requiredFunction: () => any; + optionalFunction?: () => any; + requiredFunctionUnion: string | (() => any); + optionalFunctionUnion?: string | (() => any); + optionalFunctionUndefined: (() => any) | undefined; + optionalFunctionUnionUndefined: string | (() => any) | undefined; +}; + +type ExpectedAppDataJson = { + requiredString: string; + requiredUnion: number | boolean; + + optionalString?: string; + optionalUnion?: string | number; + optionalStringUndefined?: string; + optionalUnionUndefined?: string | number; +}; + +declare const response: Jsonify; + +expectType(response);