Skip to content

Commit

Permalink
fix(types): JSONParsed supports interface and Date etc. (#1853)
Browse files Browse the repository at this point in the history
* fix(types): `JSONParsed` supports interface and `Date` etc.

* denoify

* supports bigint as never

* denoify
  • Loading branch information
yusukebe committed Jan 2, 2024
1 parent d5d2e23 commit 571c284
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 6 deletions.
15 changes: 12 additions & 3 deletions deno_dist/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = {
[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> = T extends { toJSON(): infer J }
? (() => J) extends () => JSONObject
? J
: JSONParsed<J>
: T extends JSONPrimitive
? T
: T extends Array<infer U>
? Array<JSONParsed<U>>
: T extends object
? { [K in keyof T]: JSONParsed<T[K]> }
: never

export type InterfaceToType<T> = T extends Function ? T : { [K in keyof T]: InterfaceToType<T[K]> }

Expand Down
71 changes: 71 additions & 0 deletions src/utils/types.test.ts
Original file line number Diff line number Diff line change
@@ -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<SampleType> = {
someMeta: {
metadata: {
href: '',
sampleEnum: SampleEnum.Value1,
},
},
}
expectTypeOf(sample).toEqualTypeOf<SampleType>()
})

it('Should parse a complex interface', () => {
const sample: JSONParsed<SampleInterface> = {
someMeta: {
metadata: {
href: '',
sampleEnum: SampleEnum.Value1,
},
},
}
expectTypeOf(sample).toEqualTypeOf<SampleInterface>()
})

it('Should convert Date to string', () => {
type Post = {
datetime: Date
}
type Expected = {
datetime: string
}
type Actual = JSONParsed<Post>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type verify = Expect<Equal<Expected, Actual>>
})

it('Should convert bigint to never', () => {
type Post = {
num: bigint
}
type Expected = {
num: never
}
type Actual = JSONParsed<Post>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type verify = Expect<Equal<Expected, Actual>>
})
})
15 changes: 12 additions & 3 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = {
[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> = T extends { toJSON(): infer J }
? (() => J) extends () => JSONObject
? J
: JSONParsed<J>
: T extends JSONPrimitive
? T
: T extends Array<infer U>
? Array<JSONParsed<U>>
: T extends object
? { [K in keyof T]: JSONParsed<T[K]> }
: never

export type InterfaceToType<T> = T extends Function ? T : { [K in keyof T]: InterfaceToType<T[K]> }

Expand Down

0 comments on commit 571c284

Please sign in to comment.