From 157ed07c87173a80f71db195e32c63f7cec03398 Mon Sep 17 00:00:00 2001 From: Nicholas Rakoto Date: Tue, 17 Oct 2023 10:41:26 +0200 Subject: [PATCH] `Jsonify`: Improve type inference for objects with `.toJSON()` extending primitives (#690) --- source/jsonify.d.ts | 36 ++++++++++++++++++------------------ test-d/jsonify.ts | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/source/jsonify.d.ts b/source/jsonify.d.ts index d429e2ddc..d226911ff 100644 --- a/source/jsonify.d.ts +++ b/source/jsonify.d.ts @@ -96,24 +96,24 @@ export type Jsonify = IsAny extends true ? null : T extends JsonPrimitive ? T - : // Instanced primitives are objects - T extends Number - ? number - : T extends String - ? string - : T extends Boolean - ? boolean - : T extends Map | Set - ? EmptyObject - : T extends TypedArray - ? Record - : T extends NotJsonable - ? never // Non-JSONable type union was found not empty - : // Any object with toJSON is special case - T extends {toJSON(): infer J} - ? (() => J) extends () => JsonValue // Is J assignable to JsonValue? - ? J // Then T is Jsonable and its Jsonable value is J - : Jsonify // Maybe if we look a level deeper we'll find a JsonValue + : // Any object with toJSON is special case + T extends {toJSON(): infer J} + ? (() => J) extends () => JsonValue // Is J assignable to JsonValue? + ? J // Then T is Jsonable and its Jsonable value is J + : Jsonify // Maybe if we look a level deeper we'll find a JsonValue + : // Instanced primitives are objects + T extends Number + ? number + : T extends String + ? string + : T extends Boolean + ? boolean + : T extends Map | Set + ? EmptyObject + : T extends TypedArray + ? Record + : T extends NotJsonable + ? never // Non-JSONable type union was found not empty : T extends [] ? [] : T extends unknown[] diff --git a/test-d/jsonify.ts b/test-d/jsonify.ts index 2c263dd1f..0d6898c6b 100644 --- a/test-d/jsonify.ts +++ b/test-d/jsonify.ts @@ -112,6 +112,21 @@ expectNotAssignable(nonJsonWithToJSON); expectAssignable(nonJsonWithToJSON.toJSON()); expectAssignable>(nonJsonWithToJSON.toJSON()); +class NonJsonExtendPrimitiveWithToJSON extends Number { + public fixture = BigInt('42'); + + public toJSON(): {fixture: string} { + return { + fixture: '42n', + }; + } +} + +const nonJsonExtendPrimitiveWithToJSON = new NonJsonExtendPrimitiveWithToJSON(); +expectNotAssignable(nonJsonExtendPrimitiveWithToJSON); +expectAssignable(nonJsonExtendPrimitiveWithToJSON.toJSON()); +expectAssignable>(nonJsonExtendPrimitiveWithToJSON.toJSON()); + class NonJsonWithToJSONWrapper { public inner: NonJsonWithToJSON = nonJsonWithToJSON; public override = 42;