From 571c28494711a6d0cbe37a32f5fd827f123b8a1e Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Mon, 1 Jan 2024 18:23:12 +0900 Subject: [PATCH] fix(types): `JSONParsed` supports interface and `Date` etc. (#1853) * fix(types): `JSONParsed` supports interface and `Date` etc. * denoify * supports bigint as never * denoify --- deno_dist/utils/types.ts | 15 +++++++-- src/utils/types.test.ts | 71 ++++++++++++++++++++++++++++++++++++++++ src/utils/types.ts | 15 +++++++-- 3 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 src/utils/types.test.ts diff --git a/deno_dist/utils/types.ts b/deno_dist/utils/types.ts index 5f4bce2037..0da3742a1b 100644 --- a/deno_dist/utils/types.ts +++ b/deno_dist/utils/types.ts @@ -27,9 +27,18 @@ export type JSONPrimitive = string | boolean | number | null | undefined export type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[] export type JSONObject = { [key: string]: JSONPrimitive | JSONArray | JSONObject | object } export type JSONValue = JSONObject | JSONArray | JSONPrimitive -export type JSONParsed = { - [k in keyof T]: T[k] extends JSONValue ? T[k] : string -} +// Non-JSON values such as `Date` implement `.toJSON()`, so they can be transformed to a value assignable to `JSONObject`: +export type JSONParsed = T extends { toJSON(): infer J } + ? (() => J) extends () => JSONObject + ? J + : JSONParsed + : T extends JSONPrimitive + ? T + : T extends Array + ? Array> + : T extends object + ? { [K in keyof T]: JSONParsed } + : never export type InterfaceToType = T extends Function ? T : { [K in keyof T]: InterfaceToType } diff --git a/src/utils/types.test.ts b/src/utils/types.test.ts new file mode 100644 index 0000000000..4f465149f6 --- /dev/null +++ b/src/utils/types.test.ts @@ -0,0 +1,71 @@ +import type { Equal, Expect, JSONParsed } from './types' + +describe('JSONParsed', () => { + enum SampleEnum { + Value1 = 'value1', + Value2 = 'value2', + } + + interface Meta { + metadata: { + href: string + sampleEnum: SampleEnum + } + } + + interface SampleInterface { + someMeta: Meta + } + + type SampleType = { + someMeta: Meta + } + + it('Should parse a complex type', () => { + const sample: JSONParsed = { + someMeta: { + metadata: { + href: '', + sampleEnum: SampleEnum.Value1, + }, + }, + } + expectTypeOf(sample).toEqualTypeOf() + }) + + it('Should parse a complex interface', () => { + const sample: JSONParsed = { + someMeta: { + metadata: { + href: '', + sampleEnum: SampleEnum.Value1, + }, + }, + } + expectTypeOf(sample).toEqualTypeOf() + }) + + it('Should convert Date to string', () => { + type Post = { + datetime: Date + } + type Expected = { + datetime: string + } + type Actual = JSONParsed + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type verify = Expect> + }) + + it('Should convert bigint to never', () => { + type Post = { + num: bigint + } + type Expected = { + num: never + } + type Actual = JSONParsed + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type verify = Expect> + }) +}) diff --git a/src/utils/types.ts b/src/utils/types.ts index 5f4bce2037..0da3742a1b 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -27,9 +27,18 @@ export type JSONPrimitive = string | boolean | number | null | undefined export type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[] export type JSONObject = { [key: string]: JSONPrimitive | JSONArray | JSONObject | object } export type JSONValue = JSONObject | JSONArray | JSONPrimitive -export type JSONParsed = { - [k in keyof T]: T[k] extends JSONValue ? T[k] : string -} +// Non-JSON values such as `Date` implement `.toJSON()`, so they can be transformed to a value assignable to `JSONObject`: +export type JSONParsed = T extends { toJSON(): infer J } + ? (() => J) extends () => JSONObject + ? J + : JSONParsed + : T extends JSONPrimitive + ? T + : T extends Array + ? Array> + : T extends object + ? { [K in keyof T]: JSONParsed } + : never export type InterfaceToType = T extends Function ? T : { [K in keyof T]: InterfaceToType }