Skip to content

Commit

Permalink
Added axios.formToJSON method; (#4735)
Browse files Browse the repository at this point in the history
* Draft

* Added `formDataToJSON` helper;
Added `axios.formToJSON` method;
Added client tests;

Co-authored-by: Jay <jasonsaayman@gmail.com>
  • Loading branch information
DigitalBrainJS and jasonsaayman committed May 25, 2022
1 parent 934f390 commit c008e57
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 10 deletions.
7 changes: 7 additions & 0 deletions index.d.ts
Expand Up @@ -300,6 +300,12 @@ export interface GenericFormData {
append(name: string, value: any, options?: any): any;
}

export interface GenericHTMLFormElement {
name: string;
method: string;
submit(): void;
}

export interface AxiosStatic extends AxiosInstance {
create(config?: CreateAxiosDefaults): AxiosInstance;
Cancel: CancelStatic;
Expand All @@ -312,6 +318,7 @@ export interface AxiosStatic extends AxiosInstance {
spread<T, R>(callback: (...args: T[]) => R): (array: T[]) => R;
isAxiosError<T = any, D = any>(payload: any): payload is AxiosError<T, D>;
toFormData(sourceObj: object, targetFormData?: GenericFormData, options?: FormSerializerOptions): GenericFormData;
formToJSON(form: GenericFormData|GenericHTMLFormElement): object;
}

declare const axios: AxiosStatic;
Expand Down
6 changes: 5 additions & 1 deletion lib/axios.js
Expand Up @@ -5,7 +5,7 @@ var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');

var formDataToJSON = require('./helpers/formDataToJSON');
/**
* Create an instance of Axios
*
Expand Down Expand Up @@ -58,6 +58,10 @@ axios.spread = require('./helpers/spread');
// Expose isAxiosError
axios.isAxiosError = require('./helpers/isAxiosError');

axios.formToJSON = function(thing) {
return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
};

module.exports = axios;

// Allow use of default import syntax in TypeScript
Expand Down
3 changes: 2 additions & 1 deletion lib/core/Axios.js
Expand Up @@ -25,7 +25,8 @@ function Axios(instanceConfig) {
/**
* Dispatch a request
*
* @param {Object} config The config specific for this request (merged with this.defaults)
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*/
Axios.prototype.request = function request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
Expand Down
27 changes: 21 additions & 6 deletions lib/defaults/index.js
Expand Up @@ -7,6 +7,7 @@ var transitionalDefaults = require('./transitional');
var toFormData = require('../helpers/toFormData');
var toURLEncodedForm = require('../helpers/toURLEncodedForm');
var platform = require('../platform');
var formDataToJSON = require('../helpers/formDataToJSON');

var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
Expand Down Expand Up @@ -55,8 +56,24 @@ var defaults = {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');

if (utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
var contentType = headers && headers['Content-Type'] || '';
var hasJSONContentType = contentType.indexOf('application/json') > -1;
var isObjectPayload = utils.isObject(data);

if (isObjectPayload && utils.isHTMLForm(data)) {
data = new FormData(data);
}

var isFormData = utils.isFormData(data);

if (isFormData) {
if (!hasJSONContentType) {
return data;
}
return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
}

if (utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
Expand All @@ -72,16 +89,14 @@ var defaults = {
return data.toString();
}

var isObjectPayload = utils.isObject(data);
var contentType = headers && headers['Content-Type'] || '';
var isFileList;

if (isObjectPayload) {
if (contentType.indexOf('application/x-www-form-urlencoded') !== -1) {
return toURLEncodedForm(data, this.formSerializer).toString();
}

if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') !== -1) {
if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
var _FormData = this.env && this.env.FormData;

return toFormData(
Expand All @@ -92,7 +107,7 @@ var defaults = {
}
}

if (isObjectPayload || contentType.indexOf('application/json') !== -1) {
if (isObjectPayload || hasJSONContentType ) {
setContentTypeIfUnset(headers, 'application/json');
return stringifySafely(data);
}
Expand Down
71 changes: 71 additions & 0 deletions lib/helpers/formDataToJSON.js
@@ -0,0 +1,71 @@
'use strict';

var utils = require('../utils');

function parsePropPath(name) {
// foo[x][y][z]
// foo.x.y.z
// foo-x-y-z
// foo x y z
return utils.matchAll(/\w+|\[(\w*)]/g, name).map(function(match) {
return match[0] === '[]' ? '' : match[1] || match[0];
});
}

function arrayToObject(arr) {
var obj = {};
var keys = Object.keys(arr);
var i;
var len = keys.length;
var key;
for (i = 0; i < len; i++) {
key = keys[i];
obj[key] = arr[key];
}
return obj;
}

function formDataToJSON(formData) {
function buildPath(path, value, target, index) {
var name = path[index++];
var isNumericKey = Number.isFinite(+name);
var isLast = index >= path.length;
name = !name && utils.isArray(target) ? target.length : name;

if (isLast) {
if (utils.hasOwnProperty(target, name)) {
target[name] = [target[name], value];
} else {
target[name] = value;
}

return !isNumericKey;
}

if (!target[name] || !utils.isObject(target[name])) {
target[name] = [];
}

var result = buildPath(path, value, target[name], index);

if (result && utils.isArray(target[name])) {
target[name] = arrayToObject(target[name]);
}

return !isNumericKey;
}

if (utils.isFormData(formData) && utils.isFunction(formData.entries)) {
var obj = {};

utils.forEachEntry(formData, function(name, value) {
buildPath(parsePropPath(name), value, obj, 0);
});

return obj;
}

return null;
}

module.exports = formDataToJSON;
38 changes: 37 additions & 1 deletion lib/utils.js
Expand Up @@ -441,6 +441,38 @@ var isTypedArray = (function(TypedArray) {
};
})(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array));

function forEachEntry(obj, fn) {
var generator = obj && obj[Symbol.iterator];

var iterator = generator.call(obj);

var result;

while ((result = iterator.next()) && !result.done) {
var pair = result.value;
fn.call(obj, pair[0], pair[1]);
}
}

function matchAll(regExp, str) {
var matches;
var arr = [];

while ((matches = regExp.exec(str)) !== null) {
arr.push(matches);
}

return arr;
}

var isHTMLForm = kindOfTest('HTMLFormElement');

var hasOwnProperty = (function resolver(_hasOwnProperty) {
return function(obj, prop) {
return _hasOwnProperty.call(obj, prop);
};
})(Object.prototype.hasOwnProperty);

module.exports = {
isArray: isArray,
isArrayBuffer: isArrayBuffer,
Expand Down Expand Up @@ -471,5 +503,9 @@ module.exports = {
endsWith: endsWith,
toArray: toArray,
isTypedArray: isTypedArray,
isFileList: isFileList
isFileList: isFileList,
forEachEntry: forEachEntry,
matchAll: matchAll,
isHTMLForm: isHTMLForm,
hasOwnProperty: hasOwnProperty
};
50 changes: 50 additions & 0 deletions test/specs/helpers/formDataToJSON.spec.js
@@ -0,0 +1,50 @@
var formDataToJSON = require('../../../lib/helpers/formDataToJSON');

describe('formDataToJSON', function () {
it('should convert a FormData Object to JSON Object', function () {
const formData = new FormData();

formData.append('foo[bar][baz]', '123');

expect(formDataToJSON(formData)).toEqual({
foo: {
bar: {
baz: '123'
}
}
});
});

it('should convert repeatable values as an array', function () {
const formData = new FormData();

formData.append('foo', '1');
formData.append('foo', '2');

expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2']
});
});

it('should convert props with empty brackets to arrays', function () {
const formData = new FormData();

formData.append('foo[]', '1');
formData.append('foo[]', '2');

expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2']
});
});

it('should supported indexed arrays', function () {
const formData = new FormData();

formData.append('foo[0]', '1');
formData.append('foo[1]', '2');

expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2']
});
});
});
3 changes: 2 additions & 1 deletion test/specs/instance.spec.js
Expand Up @@ -25,7 +25,8 @@ describe('instance', function () {
'isAxiosError',
'VERSION',
'default',
'toFormData'
'toFormData',
'formToJSON'
].indexOf(prop) > -1) {
continue;
}
Expand Down

0 comments on commit c008e57

Please sign in to comment.