From 2eea67e8d857407ada2e21f33735b322f8ded0de Mon Sep 17 00:00:00 2001 From: Reverier Xu Date: Sat, 1 Jun 2024 00:47:38 +0800 Subject: [PATCH] Add `stringifyJson` option (#579) Co-authored-by: Sindre Sorhus --- readme.md | 25 +++++++++++++++++++++++++ source/core/Ky.ts | 2 +- source/core/constants.ts | 1 + source/types/options.ts | 26 ++++++++++++++++++++++++++ test/main.ts | 20 ++++++++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 53fb066b..d410de48 100644 --- a/readme.md +++ b/readme.md @@ -428,6 +428,31 @@ const json = await ky('https://example.com', { }).json(); ``` +##### stringifyJson + +Type: `Function`\ +Default: `JSON.stringify()` + +User-defined JSON-stringifying function. + +Use-cases: +1. Stringify JSON with a custom `replacer` function. + +```js +import ky from 'ky'; +import {DateTime} from 'luxon'; + +const json = await ky('https://example.com', { + stringifyJson: data => JSON.stringify(data, (key, value) => { + if (key.endsWith('_at')) { + return DateTime.fromISO(value).toSeconds(); + } + + return value; + }) +}).json(); +``` + ##### fetch Type: `Function`\ diff --git a/source/core/Ky.ts b/source/core/Ky.ts index 638cb324..8477a2cf 100644 --- a/source/core/Ky.ts +++ b/source/core/Ky.ts @@ -203,7 +203,7 @@ export class Ky { } if (this._options.json !== undefined) { - this._options.body = JSON.stringify(this._options.json); + this._options.body = this._options.stringifyJson?.(this._options.json) ?? JSON.stringify(this._options.json); this.request.headers.set('content-type', this._options.headers.get('content-type') ?? 'application/json'); this.request = new globalThis.Request(this.request, {body: this._options.body}); } diff --git a/source/core/constants.ts b/source/core/constants.ts index 27309629..50f948d4 100644 --- a/source/core/constants.ts +++ b/source/core/constants.ts @@ -50,6 +50,7 @@ export const stop = Symbol('stop'); export const kyOptionKeys: KyOptionsRegistry = { json: true, parseJson: true, + stringifyJson: true, searchParams: true, prefixUrl: true, retry: true, diff --git a/source/types/options.ts b/source/types/options.ts index c391dbaa..22fd0651 100644 --- a/source/types/options.ts +++ b/source/types/options.ts @@ -58,6 +58,32 @@ export type KyOptions = { */ parseJson?: (text: string) => unknown; + /** + User-defined JSON-stringifying function. + + Use-cases: + 1. Stringify JSON with a custom `replacer` function. + + @default JSON.stringify() + + @example + ``` + import ky from 'ky'; + import {DateTime} from 'luxon'; + + const json = await ky('https://example.com', { + stringifyJson: data => JSON.stringify(data, (key, value) => { + if (key.endsWith('_at')) { + return DateTime.fromISO(value).toSeconds(); + } + + return value; + }) + }).json(); + ``` + */ + stringifyJson?: (data: unknown) => string; + /** Search parameters to include in the request URL. Setting this will override all existing search parameters in the input URL. diff --git a/test/main.ts b/test/main.ts index 7c9933eb..7d02c58d 100644 --- a/test/main.ts +++ b/test/main.ts @@ -733,3 +733,23 @@ test('parseJson option with promise.json() shortcut', async t => { await server.close(); }); + +test('stringifyJson option with request.json()', async t => { + const server = await createHttpTestServer({bodyParser: false}); + + const json = {hello: 'world'}; + const extra = 'extraValue'; + + server.post('/', async (request, response) => { + const body = await parseRawBody(request); + t.is(body, JSON.stringify({data: json, extra})); + response.end(); + }); + + await ky.post(server.url, { + stringifyJson: data => JSON.stringify({data, extra}), + json, + }); + + await server.close(); +});