mirror of
https://github.com/honojs/hono.git
synced 2024-11-24 11:07:29 +01:00
feat(types): improve JSONParsed (#3074)
* feat: improve JSONParsed * refactor: make things simple
This commit is contained in:
parent
6c1b2a7c01
commit
2a4f257f36
@ -11,7 +11,13 @@ import type {
|
||||
} from './types'
|
||||
import { HtmlEscapedCallbackPhase, resolveCallback } from './utils/html'
|
||||
import type { RedirectStatusCode, StatusCode } from './utils/http-status'
|
||||
import type { IsAny, JSONParsed, JSONValue, SimplifyDeepArray } from './utils/types'
|
||||
import type {
|
||||
InvalidJSONValue,
|
||||
IsAny,
|
||||
JSONParsed,
|
||||
JSONValue,
|
||||
SimplifyDeepArray,
|
||||
} from './utils/types'
|
||||
|
||||
type HeaderRecord = Record<string, string | string[]>
|
||||
|
||||
@ -142,12 +148,18 @@ interface TextRespond {
|
||||
* @returns {JSONRespondReturn<T, U>} - The response after rendering the JSON object, typed with the provided object and status code types.
|
||||
*/
|
||||
interface JSONRespond {
|
||||
<T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode = StatusCode>(
|
||||
<
|
||||
T extends JSONValue | SimplifyDeepArray<unknown> | InvalidJSONValue,
|
||||
U extends StatusCode = StatusCode
|
||||
>(
|
||||
object: T,
|
||||
status?: U,
|
||||
headers?: HeaderRecord
|
||||
): JSONRespondReturn<T, U>
|
||||
<T extends JSONValue | SimplifyDeepArray<unknown>, U extends StatusCode = StatusCode>(
|
||||
<
|
||||
T extends JSONValue | SimplifyDeepArray<unknown> | InvalidJSONValue,
|
||||
U extends StatusCode = StatusCode
|
||||
>(
|
||||
object: T,
|
||||
init?: ResponseInit
|
||||
): JSONRespondReturn<T, U>
|
||||
@ -160,7 +172,7 @@ interface JSONRespond {
|
||||
* @returns {Response & TypedResponse<SimplifyDeepArray<T> extends JSONValue ? (JSONValue extends SimplifyDeepArray<T> ? never : JSONParsed<T>) : never, U, 'json'>} - The response after rendering the JSON object, typed with the provided object and status code types.
|
||||
*/
|
||||
type JSONRespondReturn<
|
||||
T extends JSONValue | SimplifyDeepArray<unknown>,
|
||||
T extends JSONValue | SimplifyDeepArray<unknown> | InvalidJSONValue,
|
||||
U extends StatusCode
|
||||
> = Response &
|
||||
TypedResponse<
|
||||
@ -168,7 +180,7 @@ type JSONRespondReturn<
|
||||
? JSONValue extends SimplifyDeepArray<T>
|
||||
? never
|
||||
: JSONParsed<T>
|
||||
: never,
|
||||
: JSONParsed<T>,
|
||||
U,
|
||||
'json'
|
||||
>
|
||||
@ -692,7 +704,7 @@ export class Context<
|
||||
* ```
|
||||
*/
|
||||
json: JSONRespond = <
|
||||
T extends JSONValue | SimplifyDeepArray<unknown>,
|
||||
T extends JSONValue | SimplifyDeepArray<unknown> | InvalidJSONValue,
|
||||
U extends StatusCode = StatusCode
|
||||
>(
|
||||
object: T,
|
||||
|
@ -375,7 +375,7 @@ describe('Support c.json(undefined)', () => {
|
||||
'/this/is/a/test': {
|
||||
$get: {
|
||||
input: {}
|
||||
output: undefined
|
||||
output: never
|
||||
outputFormat: 'json'
|
||||
status: StatusCode
|
||||
}
|
||||
|
@ -21,6 +21,147 @@ describe('JSONParsed', () => {
|
||||
someMeta: Meta
|
||||
}
|
||||
|
||||
describe('primitives', () => {
|
||||
it('should convert number type to number', () => {
|
||||
type Actual = JSONParsed<number>
|
||||
type Expected = number
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert string type to string', () => {
|
||||
type Actual = JSONParsed<string>
|
||||
type Expected = string
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert boolean type to boolean', () => {
|
||||
type Actual = JSONParsed<boolean>
|
||||
type Expected = boolean
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert null type to null', () => {
|
||||
type Actual = JSONParsed<null>
|
||||
type Expected = null
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('toJSON', () => {
|
||||
it('should convert { toJSON() => T } to T', () => {
|
||||
type Actual = JSONParsed<{ toJSON(): number }>
|
||||
type Expected = number
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('toJSON is not called recursively', () => {
|
||||
type Actual = JSONParsed<{ toJSON(): { toJSON(): number } }>
|
||||
type Expected = {}
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert { a: { toJSON() => T } } to { a: T }', () => {
|
||||
type Actual = JSONParsed<{ a: { toJSON(): number } }>
|
||||
type Expected = { a: number }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('invalid types', () => {
|
||||
it('should convert undefined type to never', () => {
|
||||
type Actual = JSONParsed<undefined>
|
||||
type Expected = never
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert symbol type to never', () => {
|
||||
type Actual = JSONParsed<symbol>
|
||||
type Expected = never
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert function type to never', () => {
|
||||
type Actual = JSONParsed<() => void>
|
||||
type Expected = never
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('array', () => {
|
||||
it('should convert undefined[] type to null[]', () => {
|
||||
type Actual = JSONParsed<undefined[]>
|
||||
type Expected = null[]
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert Function[] type to null[]', () => {
|
||||
type Actual = JSONParsed<(() => void)[]>
|
||||
type Expected = null[]
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert symbol[] type to null[]', () => {
|
||||
type Actual = JSONParsed<symbol[]>
|
||||
type Expected = null[]
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert (T | undefined)[] type to JSONParsedT | null>[]', () => {
|
||||
type Actual = JSONParsed<(number | undefined)[]>
|
||||
type Expected = (number | null)[]
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('tuple', () => {
|
||||
it('should convert [T, S] type to [T, S]', () => {
|
||||
type Actual = JSONParsed<[number, string]>
|
||||
type Expected = [number, string]
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert [T, undefined] type to [T, null]', () => {
|
||||
type Actual = JSONParsed<[number, undefined]>
|
||||
type Expected = [number, null]
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('object', () => {
|
||||
it('should omit keys with undefined value', () => {
|
||||
type Actual = JSONParsed<{ a: number; b: undefined }>
|
||||
type Expected = { a: number }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should omit keys with symbol value', () => {
|
||||
type Actual = JSONParsed<{ a: number; b: symbol }>
|
||||
type Expected = { a: number }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should omit keys with function value', () => {
|
||||
type Actual = JSONParsed<{ a: number; b: () => void }>
|
||||
type Expected = { a: number }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should omit symbol keys', () => {
|
||||
type Actual = JSONParsed<{ a: number; [x: symbol]: number }>
|
||||
type Expected = { a: number }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert T | undefined to T | undefined', () => {
|
||||
type Actual = JSONParsed<{ a: number; b: number | undefined }>
|
||||
type Expected = { a: number; b: number | undefined }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should omit keys with invalid union', () => {
|
||||
type Actual = JSONParsed<{ a: number; b: undefined | symbol }>
|
||||
type Expected = { a: number }
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Set/Map', () => {
|
||||
it('should convert Set to empty object', () => {
|
||||
type Actual = JSONParsed<Set<number>>
|
||||
type Expected = {}
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
it('should convert Map to empty object', () => {
|
||||
type Actual = JSONParsed<Map<number, number>>
|
||||
type Expected = {}
|
||||
expectTypeOf<Actual>().toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
it('Should parse a complex type', () => {
|
||||
const sample: JSONParsed<SampleType> = {
|
||||
someMeta: {
|
||||
|
@ -24,21 +24,46 @@ export type RemoveBlankRecord<T> = T extends Record<infer K, unknown>
|
||||
|
||||
export type IfAnyThenEmptyObject<T> = 0 extends 1 & T ? {} : T
|
||||
|
||||
export type JSONPrimitive = string | boolean | number | null | undefined
|
||||
export type JSONPrimitive = string | boolean | number | null
|
||||
export type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[]
|
||||
export type JSONObject = { [key: string]: JSONPrimitive | JSONArray | JSONObject | object }
|
||||
export type InvalidJSONValue = undefined | symbol | ((...args: unknown[]) => unknown)
|
||||
|
||||
type InvalidToNull<T> = T extends InvalidJSONValue ? null : T
|
||||
|
||||
type IsInvalid<T> = T extends InvalidJSONValue ? true : false
|
||||
|
||||
/**
|
||||
* symbol keys are omitted through `JSON.stringify`
|
||||
*/
|
||||
type OmitSymbolKeys<T> = { [K in keyof T as K extends symbol ? never : K]: T[K] }
|
||||
|
||||
export type JSONValue = JSONObject | JSONArray | JSONPrimitive
|
||||
// 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) extends () => JSONPrimitive
|
||||
? J
|
||||
: (() => J) extends () => { toJSON(): unknown }
|
||||
? {}
|
||||
: JSONParsed<J>
|
||||
: T extends JSONPrimitive
|
||||
? T
|
||||
: T extends InvalidJSONValue
|
||||
? never
|
||||
: T extends []
|
||||
? []
|
||||
: T extends readonly [infer R, ...infer U]
|
||||
? [JSONParsed<InvalidToNull<R>>, ...JSONParsed<U>]
|
||||
: T extends Array<infer U>
|
||||
? Array<JSONParsed<U>>
|
||||
? Array<JSONParsed<InvalidToNull<U>>>
|
||||
: T extends Set<unknown> | Map<unknown, unknown>
|
||||
? {}
|
||||
: T extends object
|
||||
? { [K in keyof T]: JSONParsed<T[K]> }
|
||||
? {
|
||||
[K in keyof OmitSymbolKeys<T> as IsInvalid<T[K]> extends true
|
||||
? never
|
||||
: K]: boolean extends IsInvalid<T[K]> ? JSONParsed<T[K]> | undefined : JSONParsed<T[K]>
|
||||
}
|
||||
: never
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user