diff --git a/src/client/client.test.ts b/src/client/client.test.ts index 94daaf42a..8cb8060b6 100644 --- a/src/client/client.test.ts +++ b/src/client/client.test.ts @@ -7,7 +7,7 @@ import { expectTypeOf, vi } from 'vitest' import { upgradeWebSocket } from '../adapter/deno/websocket' import { Hono } from '../hono' import { parse } from '../utils/cookie' -import type { Equal, Expect } from '../utils/types' +import type { Equal, Expect, JSONValue, SimplifyDeepArray } from '../utils/types' import { validator } from '../validator' import { hc } from './client' import type { ClientResponse, InferRequestType, InferResponseType } from './types' @@ -542,6 +542,22 @@ describe('Merge path with `app.route()`', () => { const data = await res.json() type verify = Expect> expect(data.ok).toBe(true) + + // A few more types only tests + interface DeepInterface { + l2: { + l3: Result + } + } + interface ExtraDeepInterface { + l4: DeepInterface + } + type verifyDeepInterface = Expect< + Equal extends JSONValue ? true : false, true> + > + type verifyExtraDeepInterface = Expect< + Equal extends JSONValue ? true : false, true> + > }) it('Should have correct types - with array of interfaces', async () => { @@ -560,6 +576,20 @@ describe('Merge path with `app.route()`', () => { const data = await res.json() type verify = Expect> expect(data[0].ok).toBe(true) + + // A few more types only tests + type verifyNestedArrayTyped = Expect< + Equal extends JSONValue ? true : false, true> + > + type verifyNestedArrayInterfaceArray = Expect< + Equal extends JSONValue ? true : false, true> + > + type verifyExtraNestedArrayTyped = Expect< + Equal extends JSONValue ? true : false, true> + > + type verifyExtraNestedArrayInterfaceArray = Expect< + Equal extends JSONValue ? true : false, true> + > }) it('Should allow a Date object and return it as a string', async () => { diff --git a/src/context.ts b/src/context.ts index aa3264b11..e5eaa5815 100644 --- a/src/context.ts +++ b/src/context.ts @@ -2,7 +2,7 @@ import type { HonoRequest } from './request' import type { Env, FetchEventLike, Input, NotFoundHandler, TypedResponse } from './types' import { HtmlEscapedCallbackPhase, resolveCallback } from './utils/html' import type { RedirectStatusCode, StatusCode } from './utils/http-status' -import type { IsAny, JSONParsed, JSONValue, Simplify, SimplifyDeepArray } from './utils/types' +import type { IsAny, JSONParsed, JSONValue, SimplifyDeepArray } from './utils/types' type HeaderRecord = Record @@ -127,38 +127,40 @@ interface TextRespond { * @param {U} [status] - An optional status code for the response. * @param {HeaderRecord} [headers] - An optional record of headers to include in the response. * - * @returns {Response & TypedResponse extends JSONValue ? (JSONValue extends SimplifyDeepArray ? never : JSONParsed) : never, U, 'json'>} - The response after rendering the JSON object, typed with the provided object and status code types. + * @returns {JSONRespondReturn} - The response after rendering the JSON object, typed with the provided object and status code types. */ interface JSONRespond { , U extends StatusCode>( object: T, status?: U, headers?: HeaderRecord - ): Response & - TypedResponse< - SimplifyDeepArray extends JSONValue - ? JSONValue extends SimplifyDeepArray - ? never - : JSONParsed - : never, - U, - 'json' - > + ): JSONRespondReturn , U extends StatusCode>( - object: SimplifyDeepArray extends JSONValue ? T : SimplifyDeepArray, + object: T, init?: ResponseInit - ): Response & - TypedResponse< - SimplifyDeepArray extends JSONValue - ? JSONValue extends SimplifyDeepArray - ? never - : JSONParsed - : never, - U, - 'json' - > + ): JSONRespondReturn } +/** + * @template T - The type of the JSON value or simplified unknown type. + * @template U - The type of the status code. + * + * @returns {Response & TypedResponse extends JSONValue ? (JSONValue extends SimplifyDeepArray ? never : JSONParsed) : 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, + U extends StatusCode +> = Response & + TypedResponse< + SimplifyDeepArray extends JSONValue + ? JSONValue extends SimplifyDeepArray + ? never + : JSONParsed + : never, + U, + 'json' + > + /** * Interface representing a function that responds with HTML content. * @@ -659,11 +661,11 @@ export class Context< * }) * ``` */ - json: JSONRespond = , U extends StatusCode>( + json: JSONRespond = , U extends StatusCode>( object: T, arg?: U | ResponseInit, headers?: HeaderRecord - ): ReturnType => { + ): JSONRespondReturn => { const body = JSON.stringify(object) this.#preparedHeaders ??= {} this.#preparedHeaders['content-type'] = 'application/json; charset=UTF-8' diff --git a/src/utils/types.ts b/src/utils/types.ts index d2c81f88e..8754a54a7 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -48,11 +48,11 @@ export type JSONParsed = T extends { toJSON(): infer J } export type Simplify = { [KeyType in keyof T]: T[KeyType] } & {} /** - * A simple extension of Simplify that works for array of interfaces. + * A simple extension of Simplify that will deeply traverse array elements. */ export type SimplifyDeepArray = T extends any[] - ? { [E in keyof T]: Simplify } - : { [KeyType in keyof T]: T[KeyType] } & {} + ? { [E in keyof T]: SimplifyDeepArray } + : Simplify export type InterfaceToType = T extends Function ? T : { [K in keyof T]: InterfaceToType }