Skip to content

Commit

Permalink
feat: append content-type if data is JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
yougyung committed Jan 12, 2025
1 parent 68dc25b commit dea168c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 9 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ The available instance methods are listed below.

## ✔️ Parsing Response

If you set a response type, you can parse the response with that type. The default type is 'json'.
If you set a response type, you can parse the response with that type.
If responseType is not set, the original data is returned. But parsed as `json` only if the content-type is `application/json`.

```tsx
const instance = fetchAX.create();
Expand Down Expand Up @@ -274,3 +275,13 @@ const instance = fetchAX.create({
| responseInterceptor | interceptor to be executed on response | (response: Response) => Response \| Promise<Response> | - |
| responseRejectedInterceptor | interceptor to handle rejected responses | (error: any) => any | - |
| requestInterceptor | interceptor to be executed on request | (requestArg: RequestInitReturnedByInterceptor) => RequestInitReturnedByInterceptor | - |

#### conditional auto default options

##### responseType

- By default, there is no default value for responseType. But it is `json` only if the content-type is `application/json`

##### headers

- By default, there is no default value for headers. But it's content-type is `application/json` only if the data is `json`
33 changes: 25 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,15 @@ export interface RequestInit extends Omit<globalThis.RequestInit, 'body'> {
/** Resposne data's type */
responseType?: ResponseType;
}

const isJson = (data: any) => {
try {
return typeof JSON.parse(data) === 'object';
} catch (e) {
return false;
}
};

const isArrayBufferView = (data: any): data is ArrayBufferView => {
return (
data &&
Expand All @@ -218,17 +227,10 @@ const isArrayBufferView = (data: any): data is ArrayBufferView => {
};

const isBodyInit = (data: any): data is BodyInit => {
const isJson = (data: any) => {
try {
return typeof JSON.parse(data) === 'object';
} catch (e) {
return false;
}
};
return (
isJson(data) || // data === 'string' 을 통해서도 JSON인지를 확인할 수 있지만 명시적으로 따지기 위해서
typeof data === 'string' ||
data instanceof ReadableStream ||
(typeof ReadableStream !== 'undefined' && data instanceof ReadableStream) ||
data instanceof Blob ||
data instanceof ArrayBuffer ||
data instanceof FormData ||
Expand Down Expand Up @@ -288,6 +290,7 @@ const applyDefaultOptionsArgs = (
);

const requestHeaders: Record<string, string> = {};

if (defaultOptions?.headers) {
new Headers(defaultOptions.headers).forEach((value, key) => {
requestHeaders[key] = value;
Expand All @@ -299,6 +302,10 @@ const applyDefaultOptionsArgs = (
});
}

if (requestInit?.data) {
appendJsonContentType(requestInit.data, requestHeaders);
}

let requestArgs = {
...defaultOptions,
...requestInit,
Expand Down Expand Up @@ -334,6 +341,16 @@ function isHttpError(response: Response) {
return response.status >= 300;
}

function appendJsonContentType(
data: Record<string, any> | BodyInit,
requestHeaders: Record<string, string>,
) {
if (isJson(JSON.stringify(data)) && !requestHeaders['content-type']) {
requestHeaders['content-type'] = 'application/json';
}
return requestHeaders;
}

function ensureBodyInit(data: BodyInit | Record<string, any>): BodyInit {
if (isBodyInit(data)) {
return data;
Expand Down
23 changes: 23 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,29 @@ describe('next-fetch', () => {
},
);
});

it('should append content-type application/json if data is a JSON object', async () => {
// given
const instance = fetchAX.create();

// when
await instance.post('https://jsonplaceholder.typicode.com/todos/1', {
test: 'test',
});

// then
expect(fetchMocked).toHaveBeenCalledWith(
'https://jsonplaceholder.typicode.com/todos/1',

{
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ test: 'test' }),
data: { test: 'test' },
method: 'POST',
throwError: true,
},
);
});
});

describe('next-fetch-error', () => {
Expand Down

0 comments on commit dea168c

Please sign in to comment.