From 35a71386f710fdae0644625b9f8490fd5ca77479 Mon Sep 17 00:00:00 2001 From: theluckystrike <51033404+theluckystrike@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:00:28 +0700 Subject: [PATCH] Type getJson() for every JSON value, with an optional type argument getJson() was typed as Promise, so bodies like 42, "text", true or null got the wrong type, and callers couldn't name the type they expect without casting through unknown. It's now generic: getJson(), still returning undefined when the body can't be decoded or parsed (#155). New .json tests in request-utils.spec.ts cover an object, an array, a number, null, invalid JSON, an unknown encoding and an explicit type argument. On main the spec doesn't compile (TS2558: Expected 0 type arguments, but got 1). With this change it passes (16 passing), tsc builds cleanly and the full test:node run gives 842 passing, 0 failing. --- src/types.ts | 8 ++++++-- src/util/request-utils.ts | 4 ++-- test/request-utils.spec.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/types.ts b/src/types.ts index 8e641cd1c..7a4f42248 100644 --- a/src/types.ts +++ b/src/types.ts @@ -244,9 +244,13 @@ export interface CompletedBody { /** * The contents of the response, decoded, parsed as UTF-8 string, and * then parsed a JSON. The response is decoded and returned asynchronously - * as a Promise. + * as a Promise. This returns undefined if the body can't be decoded or + * isn't valid JSON. + * + * The result covers any JSON value by default, or you can pass a type + * argument to set the type you expect. */ - getJson(): Promise; + getJson(): Promise; /** * The contents of the response, decoded, and then parsed automatically as diff --git a/src/util/request-utils.ts b/src/util/request-utils.ts index 947b76dab..2eaa540a8 100644 --- a/src/util/request-utils.ts +++ b/src/util/request-utils.ts @@ -230,9 +230,9 @@ export const buildBodyReader = ( (await this.getDecodedBuffer())!.toString() ); }, - async getJson() { + async getJson() { return runAsyncOrUndefined(async () => - JSON.parse((await completedBody.getText())!) + JSON.parse((await completedBody.getText())!) as R ) }, async getUrlEncodedFormData() { diff --git a/test/request-utils.spec.ts b/test/request-utils.spec.ts index 25090306e..ac114772a 100644 --- a/test/request-utils.spec.ts +++ b/test/request-utils.spec.ts @@ -89,6 +89,44 @@ nodeOnly(() => { }); }); + describe(".json", () => { + it('parses JSON objects', async () => { + const body = buildBodyReader(Buffer.from('{"a":1}'), {}); + expect(await body.getJson()).to.deep.equal({ a: 1 }); + }); + + it('parses JSON arrays', async () => { + const body = buildBodyReader(Buffer.from('[1,2]'), {}); + expect(await body.getJson()).to.deep.equal([1, 2]); + }); + + it('parses JSON primitives', async () => { + const numberBody = buildBodyReader(Buffer.from('42'), {}); + expect(await numberBody.getJson()).to.equal(42); + + const nullBody = buildBodyReader(Buffer.from('null'), {}); + expect(await nullBody.getJson()).to.equal(null); + }); + + it('can return an explicitly typed result', async () => { + const body = buildBodyReader(Buffer.from('{"a":1}'), {}); + const json = await body.getJson<{ a: number }>(); + expect(json?.a).to.equal(1); + }); + + it('is undefined for invalid JSON', async () => { + const body = buildBodyReader(Buffer.from('{oops'), {}); + expect(await body.getJson()).to.equal(undefined); + }); + + it('is undefined for unknown encodings', async () => { + const body = buildBodyReader(Buffer.from('{"a":1}'), { + 'content-encoding': 'randomized' + }); + expect(await body.getJson()).to.equal(undefined); + }); + }); + }); describe("preprocessRequest", () => {