Skip to content

Commit

Permalink
Parse empty response bodies without throwing when using .json() (#459)
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonadern committed Oct 31, 2022
1 parent dddf7ba commit 1cc6bbb
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 7 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The `input` and `options` are the same as [`fetch`](https://developer.mozilla.or
- The `credentials` option is `same-origin` by default, which is the default in the spec too, but not all browsers have caught up yet.
- Adds some more options. See below.

Returns a [`Response` object](https://developer.mozilla.org/en-US/docs/Web/API/Response) with [`Body` methods](https://developer.mozilla.org/en-US/docs/Web/API/Body#Methods) added for convenience. So you can, for example, call `ky.get(input).json()` directly without having to await the `Response` first. When called like that, an appropriate `Accept` header will be set depending on the body method used. Unlike the `Body` methods of `window.Fetch`; these will throw an `HTTPError` if the response status is not in the range of `200...299`. Also, `.json()` will return an empty string if the response status is `204` instead of throwing a parse error due to an empty body.
Returns a [`Response` object](https://developer.mozilla.org/en-US/docs/Web/API/Response) with [`Body` methods](https://developer.mozilla.org/en-US/docs/Web/API/Body#Methods) added for convenience. So you can, for example, call `ky.get(input).json()` directly without having to await the `Response` first. When called like that, an appropriate `Accept` header will be set depending on the body method used. Unlike the `Body` methods of `window.Fetch`; these will throw an `HTTPError` if the response status is not in the range of `200...299`. Also, `.json()` will return an empty string if body is empty or the response status is `204` instead of throwing a parse error due to an empty body.

### ky.get(input, options?)
### ky.post(input, options?)
Expand Down
5 changes: 5 additions & 0 deletions source/core/Ky.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export class Ky {
return '';
}

const contentLength = response.headers.get('Content-Length');
if (contentLength === null || contentLength === '0') {
return '';
}

if (options.parseJson) {
return options.parseJson(await response.text());
}
Expand Down
2 changes: 1 addition & 1 deletion source/types/ResponsePromise.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
Returns a `Response` object with `Body` methods added for convenience. So you can, for example, call `ky.get(input).json()` directly without having to await the `Response` first. When called like that, an appropriate `Accept` header will be set depending on the body method used. Unlike the `Body` methods of `window.Fetch`; these will throw an `HTTPError` if the response status is not in the range of `200...299`. Also, `.json()` will return an empty string if the response status is `204` instead of throwing a parse error due to an empty body.
Returns a `Response` object with `Body` methods added for convenience. So you can, for example, call `ky.get(input).json()` directly without having to await the `Response` first. When called like that, an appropriate `Accept` header will be set depending on the body method used. Unlike the `Body` methods of `window.Fetch`; these will throw an `HTTPError` if the response status is not in the range of `200...299`. Also, `.json()` will return an empty string if body is empty or the response status is `204` instead of throwing a parse error due to an empty body.
*/
import {KyResponse} from './response.js';

Expand Down
24 changes: 19 additions & 5 deletions test/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,18 +236,32 @@ test('.json() with custom accept header', async t => {
await server.close();
});

test('.json() with 200 response and empty body', async t => {
test('.json() with invalid JSON body', async t => {
const server = await createHttpTestServer();
server.get('/', async (request, response) => {
t.is(request.headers.accept, 'application/json');
response.end('not json');
});

await t.throwsAsync(ky.get(server.url).json(), {
message: /Unexpected token/,
});

await server.close();
});

test('.json() with empty body', async t => {
t.plan(2);

const server = await createHttpTestServer();
server.get('/', async (request, response) => {
t.is(request.headers.accept, 'application/json');
response.status(200).end();
response.end();
});

await t.throwsAsync(ky(server.url).json(), {
message: /Unexpected end of JSON input/,
});
const responseJson = await ky.get(server.url).json();

t.is(responseJson, '');

await server.close();
});
Expand Down

0 comments on commit 1cc6bbb

Please sign in to comment.