Skip to content
D
Documentation

Send and validate JSON

how-to
2 min readUpdated

Use ky to send a JSON body and consume the response in one expression. Pass a type parameter when you know the response shape; pass a Standard Schema when you need runtime validation.

The examples use absolute URLs and run in the browser or Node.js. Replace https://api.example.com with your service's origin. Install Ky in your project:

bash
npm install ky

1. Send JSON and read the response

Send an array when your endpoint accepts a batch of records; see the quick start for basic JSON serialization and headers.

ts
import ky from 'ky';

async function main() {
	const result = await ky.post('https://api.example.com/users', {
		json: [{name: 'Ada'}, {name: 'Grace'}],
	}).json();

	console.log(result);
}

main().catch(console.error);

This sends two records in one JSON array and logs your service's parsed response; see How Ky works for the single-record example and the quick start for shortcut behavior and the default response type.

2. Give the response a TypeScript type

Use .json<User>() to get a Promise<User>. The type parameter describes your application data; it does not validate the response at runtime.

ts
import ky from 'ky';

type User = {
	name: string;
};

async function main() {
	const user = await ky.get('https://api.example.com/users/1').json<User>();

	console.log(user.name);
}

main().catch(console.error);

TypeScript lets you access user.name as a string. You can also put the type parameter on the request, as in ky.get<User>(url).json(). Use a schema instead when you need to check what the server actually sends.

3. Validate the response with a schema

Pass a Standard Schema compatible validator, such as Zod 3.24+, to .json(schema) to get the schema's validated output; see Handle request errors for handling SchemaValidationError.

Install Zod for this example:

bash
npm install zod
ts
import ky, {SchemaValidationError} from 'ky';
import {z} from 'zod';

const userSchema = z.object({name: z.string()});

async function main() {
	try {
		const result = await ky.get('https://api.example.com/users/1').json(userSchema);
		const user = userSchema.parse(result);
		console.log(user.name);
	} catch (error) {
		if (error instanceof SchemaValidationError) {
			console.error(error.issues);
		} else {
			throw error;
		}
	}
}

main().catch(console.error);

With a valid response, user.name is a string inferred from the schema. With an invalid response, the catch block logs the issues instead. For handling request failures alongside schema rejection, see Handle request errors.

4. Customize serialization and parsing

Use stringifyJson to change the outgoing representation with a replacer. This example omits internalNote from the serialized body while retaining name:

ts
import ky from 'ky';

async function main() {
	const result = await ky.post('https://api.example.com/users', {
		json: {name: 'Ada', internalNote: 'Local draft'},
		stringifyJson: data => {
			const text = JSON.stringify(data, (key, value: unknown) =>
				key === 'internalNote' ? undefined : value,
			);
			if (text === undefined) {
				throw new TypeError('The request value cannot be serialized to JSON');
			}
			return text;
		},
	}).json();

	console.log(result);
}

main().catch(console.error);

Use parseJson to change how Ky consumes response text. It receives the text and a context object containing request and response. Calling .json() without a schema on an empty response body throws because the body cannot be parsed. Provide custom empty-body handling with parseJson:

ts
import ky from 'ky';

async function main() {
	const result = await ky.get('https://api.example.com/users/1', {
		parseJson: (text, {request, response}) => {
			console.log(`Parsing JSON from ${request.url} (status: ${response.status})`);
			if (text.trim() === '') {
				return null;
			}
			const parsed: unknown = JSON.parse(text);
			return parsed;
		},
	}).json();

	console.log(result);
}

main().catch(console.error);

This returns null for an empty or whitespace-only body and parsed JSON otherwise. Inspect the URL and status in your own request's log. When you also pass a schema to .json(schema), Ky runs parseJson before validation, so the schema must accept the value your parser returns.

Options that matter

OptionTypeDefaultWhat it does
jsonunknownNot setSerializes an outgoing value into the request body. Use a value accepted by JSON.stringify().
stringifyJson(data: unknown) => stringJSON.stringify()Replaces outgoing JSON serialization.
parseJson(text: string, context: {request: Request; response: Response}) => unknownJSON.parse()Replaces incoming JSON parsing, including custom empty-body handling.

For JSON content-type headers, see the quick start.

Was this page helpful?

Send and validate JSON — ky · GPT-6.1 Sol