Repository: sindresorhus/got (ts)

Issue:
Support posting Arrays with json option
Eventhough it was mentioned in #174:

> I'm ok with also supporting an array

Arrays are not supported with the `json` option (because of `isPlainObject`) so this fails:
```js
got.post('example.com', {
  json: true,
  body: [1, 2, 3]
})
```
Could you use something like [lodash's `isObjectLike`](https://lodash.com/docs#isObjectLike) instead of `isPlainObject` or test both `isPlainObject || isArray`?


Pull request description:
Support Array with the `json` option
Allow to post Arrays with the `json` option.
This was discussed in [#174](https://github.com/sindresorhus/got/issues/174#issuecomment-299534498) but not implemented.

> I'm ok with also supporting an array

Fixes #334  

Patch:
diff --git a/index.js b/index.js
index 9ccd757..4ad2067 100644
--- a/index.js
+++ b/index.js
@@ -315,17 +315,18 @@ function normalizeArguments(url, opts) {
 			throw new TypeError('options.body must be a ReadableStream, string, Buffer or plain Object');
 		}
 
-		if ((opts.form || opts.json) && !isPlainObj(body)) {
-			throw new TypeError('options.body must be a plain Object when options.form or options.json is used');
+		const canBodyBeStringified = isPlainObj(body) || Array.isArray(body);
+		if ((opts.form || opts.json) && !canBodyBeStringified) {
+			throw new TypeError('options.body must be a plain Object or Array when options.form or options.json is used');
 		}
 
 		if (isStream(body) && typeof body.getBoundary === 'function') {
 			// Special case for https://github.com/form-data/form-data
 			headers['content-type'] = headers['content-type'] || `multipart/form-data; boundary=${body.getBoundary()}`;
-		} else if (opts.form && isPlainObj(body)) {
+		} else if (opts.form && canBodyBeStringified) {
 			headers['content-type'] = headers['content-type'] || 'application/x-www-form-urlencoded';
 			opts.body = querystring.stringify(body);
-		} else if (opts.json && isPlainObj(body)) {
+		} else if (opts.json && canBodyBeStringified) {
 			headers['content-type'] = headers['content-type'] || 'application/json';
 			opts.body = JSON.stringify(body);
 		}
@@ -388,7 +389,9 @@ function got(url, opts) {
 	}
 }
 
-const helpers = [
+got.stream = (url, opts) => asStream(normalizeArguments(url, opts));
+
+const methods = [
 	'get',
 	'post',
 	'put',
@@ -397,14 +400,9 @@ const helpers = [
 	'delete'
 ];
 
-helpers.forEach(el => {
-	got[el] = (url, opts) => got(url, Object.assign({}, opts, {method: el}));
-});
-
-got.stream = (url, opts) => asStream(normalizeArguments(url, opts));
-
-for (const el of helpers) {
-	got.stream[el] = (url, opts) => got.stream(url, Object.assign({}, opts, {method: el}));
+for (const method of methods) {
+	got[method] = (url, opts) => got(url, Object.assign({}, opts, {method}));
+	got.stream[method] = (url, opts) => got.stream(url, Object.assign({}, opts, {method}));
 }
 
 class StdError extends Error {
diff --git a/readme.md b/readme.md
index 4508e9c..074530f 100644
--- a/readme.md
+++ b/readme.md
@@ -110,7 +110,7 @@ Default: `false`
 
 If set to `true` and `Content-Type` header is not set, it will be set to `application/x-www-form-urlencoded`.
 
-`body` must be a plain object and will be stringified.
+`body` must be a plain object or array and will be stringified.
 
 ###### json
 
@@ -123,7 +123,7 @@ If set to `true` and `Content-Type` header is not set, it will be set to `applic
 
 Parse response body with `JSON.parse` and set `accept` header to `application/json`. If used in conjunction with the `form` option, the `body` will the stringified as querystring and the response parsed as JSON.
 
-`body` must be a plain object and will be stringified.
+`body` must be a plain object or array and will be stringified.
 
 ###### query
 


Test patch:
diff --git a/test/post.js b/test/post.js
index 4912e21..3b4af84 100644
--- a/test/post.js
+++ b/test/post.js
@@ -53,6 +53,14 @@ test('sends plain objects as forms', async t => {
 	t.is(body, 'such=wow');
 });
 
+test('sends arrays as forms', async t => {
+	const {body} = await got(s.url, {
+		body: ['such', 'wow'],
+		form: true
+	});
+	t.is(body, '0=such&1=wow');
+});
+
 test('sends plain objects as JSON', async t => {
 	const {body} = await got(s.url, {
 		body: {such: 'wow'},
@@ -61,6 +69,14 @@ test('sends plain objects as JSON', async t => {
 	t.deepEqual(body, {such: 'wow'});
 });
 
+test('sends arrays as JSON', async t => {
+	const {body} = await got(s.url, {
+		body: ['such', 'wow'],
+		json: true
+	});
+	t.deepEqual(body, ['such', 'wow']);
+});
+
 test('works with empty post response', async t => {
 	const {body} = await got(`${s.url}/empty`, {body: 'wow'});
 	t.is(body, '');
@@ -121,11 +137,11 @@ test('content-type header is not overriden when object in options.body', async t
 	t.is(headers['content-type'], 'doge');
 });
 
-test('throws when json body is not a plain object', async t => {
+test('throws when json body is not a plain object or array', async t => {
 	await t.throws(got(`${s.url}`, {body: '{}', json: true}), TypeError);
 });
 
-test('throws when form body is not a plain object', async t => {
+test('throws when form body is not a plain object or array', async t => {
 	await t.throws(got(`${s.url}`, {body: 'such=wow', form: true}), TypeError);
 });