Repository: node-fetch/node-fetch (js)

Issue:
Impl "Response.json"
<!--- Provide a clear and concise description of what the bug is. -->

**Reproduction**

Steps to reproduce the behavior:

1. `new Response.json({ foo: 'bar' })`



Pull request:
feat: add static Response.json
<!-- Thanks for contributing! -->

## Purpose

Implements static `Response.json` from https://github.com/whatwg/fetch/pull/1392

## Changes

Adds static Response.json

## Additional information


___

<!-- Mark the ones you have done and remove unnecessary ones. Add new tasks that fit (like TODOs). -->
- [ ] I updated readme
- [x] I added unit test(s)

___

<!-- Add `- fix #_NUMBER_` line for every PR/Issue this PR solves. Do not comma separate them. -->
- Fixes #1669


Patch:
diff --git a/@types/index.d.ts b/@types/index.d.ts
index 2374801..f68dd28 100644
--- a/@types/index.d.ts
+++ b/@types/index.d.ts
@@ -196,6 +196,7 @@ export class Response extends BodyMixin {
 
 	static error(): Response;
 	static redirect(url: string, status?: number): Response;
+	static json(data: any, init?: ResponseInit): Response;
 }
 
 export class FetchError extends Error {
diff --git a/src/response.js b/src/response.js
index 63af267..9806c0c 100644
--- a/src/response.js
+++ b/src/response.js
@@ -124,6 +124,25 @@ export default class Response extends Body {
 		return response;
 	}
 
+	static json(data = undefined, init = {}) {
+		const body = JSON.stringify(data);
+
+		if (body === undefined) {
+			throw new TypeError('data is not JSON serializable');
+		}
+
+		const headers = new Headers(init && init.headers);
+
+		if (!headers.has('content-type')) {
+			headers.set('content-type', 'application/json');
+		}
+
+		return new Response(body, {
+			...init,
+			headers
+		});
+	}
+
 	get [Symbol.toStringTag]() {
 		return 'Response';
 	}


Test patch:
diff --git a/@types/index.test-d.ts b/@types/index.test-d.ts
index 6277665..3272a0e 100644
--- a/@types/index.test-d.ts
+++ b/@types/index.test-d.ts
@@ -93,6 +93,11 @@ async function run() {
 
 	expectType<Response>(Response.redirect('https://google.com'));
 	expectType<Response>(Response.redirect('https://google.com', 301));
+
+	expectType<Response>(Response.json({foo: 'bar'}));
+	expectType<Response>(Response.json({foo: 'bar'}, {
+		status: 301
+	}));
 }
 
 run().finally(() => {
diff --git a/test/main.js b/test/main.js
index ec58f82..93e0cee 100644
--- a/test/main.js
+++ b/test/main.js
@@ -2281,6 +2281,33 @@ describe('node-fetch', () => {
 		const res = await fetch(url);
 		expect(res.url).to.equal(`${base}m%C3%B6bius`);
 	});
+
+	it('static Response.json should work', async () => {
+		const response = Response.json({foo: 'bar'});
+		expect(response.status).to.equal(200);
+		expect(response.headers.get('content-type')).to.equal('application/json');
+		expect(await response.text()).to.equal(JSON.stringify({foo: 'bar'}));
+
+		const response1 = Response.json(null, {
+			status: 301,
+			statusText: 'node-fetch',
+			headers: {
+				'Content-Type': 'text/plain'
+			}
+		});
+
+		expect(response1.headers.get('content-type')).to.equal('text/plain');
+		expect(response1.status).to.equal(301);
+		expect(response1.statusText).to.equal('node-fetch');
+
+		const response2 = Response.json(null, {
+			headers: {
+				'CoNtEnT-TypE': 'text/plain'
+			}
+		});
+
+		expect(response2.headers.get('content-type')).to.equal('text/plain');
+	});
 });
 
 describe('node-fetch using IPv6', () => {