How can I can convert my JS Object to FormData?
The reason why I want to do this is, I have an object that I constructed out of the ~100 form field values.
var item = {
description: 'Some Item',
price : '0.00',
srate : '0.00',
color : 'red',
...
...
}
Now I am asked to add the upload file functionality to my form which, of-course is impossible via JSON and so I am planning on moving to FormData. So is there any way that I can convert my JS object to FormData?
If you have an object, you can easily create a FormData object and append the names and values from that object to formData.
You haven't posted any code, so it's a general example;
var form_data = new FormData();
for ( var key in item ) {
form_data.append(key, item[key]);
}
$.ajax({
url : 'http://example.com/upload.php',
data : form_data,
processData : false,
contentType : false,
type: 'POST'
}).done(function(data){
// do stuff
});
There are more examples in the documentation on MDN
With ES6 and a more functional programming approach #adeneo's answer could looks like this:
function getFormData(object) {
const formData = new FormData();
Object.keys(object).forEach(key => formData.append(key, object[key]));
return formData;
}
And alternatively using .reduce() and arrow-functions:
const getFormData = object => Object.keys(object).reduce((formData, key) => {
formData.append(key, object[key]);
return formData;
}, new FormData());
This function adds all data from object to FormData
ES6 version from #developer033:
function buildFormData(formData, data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach(key => {
buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key);
});
} else {
const value = data == null ? '' : data;
formData.append(parentKey, value);
}
}
function jsonToFormData(data) {
const formData = new FormData();
buildFormData(formData, data);
return formData;
}
const my_data = {
num: 1,
falseBool: false,
trueBool: true,
empty: '',
und: undefined,
nullable: null,
date: new Date(),
name: 'str',
another_object: {
name: 'my_name',
value: 'whatever'
},
array: [
{
key1: {
name: 'key1'
}
}
]
};
jsonToFormData(my_data)
jQuery version:
function appendFormdata(FormData, data, name){
name = name || '';
if (typeof data === 'object'){
$.each(data, function(index, value){
if (name == ''){
appendFormdata(FormData, value, index);
} else {
appendFormdata(FormData, value, name + '['+index+']');
}
})
} else {
FormData.append(name, data);
}
}
var formData = new FormData(),
your_object = {
name: 'test object',
another_object: {
name: 'and other objects',
value: 'whatever'
}
};
appendFormdata(formData, your_object);
Try JSON.stringify function as below
var postData = JSON.stringify(item);
var formData = new FormData();
formData.append("postData",postData );
The other answers were incomplete for me. I started from #Vladimir Novopashin answer and modified it. Here are the things, that I needed and bug I found:
Support for file
Support for array
Bug: File inside complex object needs to be added with .prop instead of [prop]. For example, formData.append('photos[0][file]', file) didn't work on google chrome, while
formData.append('photos[0].file', file) worked
Ignore some properties in my object
The following code should work on IE11 and evergreen browsers.
function objectToFormData(obj, rootName, ignoreList) {
var formData = new FormData();
function appendFormData(data, root) {
if (!ignore(root)) {
root = root || '';
if (data instanceof File) {
formData.append(root, data);
} else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
appendFormData(data[i], root + '[' + i + ']');
}
} else if (typeof data === 'object' && data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (root === '') {
appendFormData(data[key], key);
} else {
appendFormData(data[key], root + '.' + key);
}
}
}
} else {
if (data !== null && typeof data !== 'undefined') {
formData.append(root, data);
}
}
}
}
function ignore(root){
return Array.isArray(ignoreList)
&& ignoreList.some(function(x) { return x === root; });
}
appendFormData(obj, rootName);
return formData;
}
function toFormData(o) {
return Object.entries(o).reduce((d,e) => (d.append(...e),d), new FormData())
}
var object = {
username: 'JohnDoe',
file: new File(['foo'], 'foo.txt', {type: 'text/plain'})
}
fetch('https://httpbin.org/post', {
method: 'POST',
body: toFormData(object)
}).then(r => r.json()).then(console.log)
I had a scenario where nested JSON had to be serialised in a linear fashion while form data is constructed, since this is how server expects values. So, I wrote a small recursive function which translates the JSON which is like this:
{
"orderPrice":"11",
"cardNumber":"************1234",
"id":"8796191359018",
"accountHolderName":"Raj Pawan",
"expiryMonth":"02",
"expiryYear":"2019",
"issueNumber":null,
"billingAddress":{
"city":"Wonderland",
"code":"8796682911767",
"firstname":"Raj Pawan",
"lastname":"Gumdal",
"line1":"Addr Line 1",
"line2":null,
"state":"US-AS",
"region":{
"isocode":"US-AS"
},
"zip":"76767-6776"
}
}
Into something like this:
{
"orderPrice":"11",
"cardNumber":"************1234",
"id":"8796191359018",
"accountHolderName":"Raj Pawan",
"expiryMonth":"02",
"expiryYear":"2019",
"issueNumber":null,
"billingAddress.city":"Wonderland",
"billingAddress.code":"8796682911767",
"billingAddress.firstname":"Raj Pawan",
"billingAddress.lastname":"Gumdal",
"billingAddress.line1":"Addr Line 1",
"billingAddress.line2":null,
"billingAddress.state":"US-AS",
"billingAddress.region.isocode":"US-AS",
"billingAddress.zip":"76767-6776"
}
The server would accept form data which is in this converted format.
Here is the function:
function jsonToFormData (inJSON, inTestJSON, inFormData, parentKey) {
// http://stackoverflow.com/a/22783314/260665
// Raj: Converts any nested JSON to formData.
var form_data = inFormData || new FormData();
var testJSON = inTestJSON || {};
for ( var key in inJSON ) {
// 1. If it is a recursion, then key has to be constructed like "parent.child" where parent JSON contains a child JSON
// 2. Perform append data only if the value for key is not a JSON, recurse otherwise!
var constructedKey = key;
if (parentKey) {
constructedKey = parentKey + "." + key;
}
var value = inJSON[key];
if (value && value.constructor === {}.constructor) {
// This is a JSON, we now need to recurse!
jsonToFormData (value, testJSON, form_data, constructedKey);
} else {
form_data.append(constructedKey, inJSON[key]);
testJSON[constructedKey] = inJSON[key];
}
}
return form_data;
}
Invocation:
var testJSON = {};
var form_data = jsonToFormData (jsonForPost, testJSON);
I am using testJSON just to see the converted results since I would not be able to extract the contents of form_data. AJAX post call:
$.ajax({
type: "POST",
url: somePostURL,
data: form_data,
processData : false,
contentType : false,
success: function (data) {
},
error: function (e) {
}
});
You can simply use:
formData.append('item', JSON.stringify(item));
2022 Update
Axios now supports automatic serialization of an object to FormData
Since version 0.27 Axios supports automatic object serialization to a FormData object if the request Content-Type header is set to multipart/form-data. Read more
Nested objects and files
Following solutions handles nested objects and arrays and files.
const buildFormData = (formData: FormData, data: FormVal, parentKey?: string) => {
if (Array.isArray(data)) {
data.forEach((el) => {
buildFormData(formData, el, parentKey)
})
} else if (typeof data === "object" && !(data instanceof File)) {
Object.keys(data).forEach((key) => {
buildFormData(formData, (data as FormDataNest)[key], parentKey ? `${parentKey}.${key}` : key)
})
} else {
if (isNil(data)) {
return
}
let value = typeof data === "boolean" || typeof data === "number" ? data.toString() : data
formData.append(parentKey as string, value)
}
}
export const getFormData = (data: Record<string, FormDataNest>) => {
const formData = new FormData()
buildFormData(formData, data)
return formData
}
Types
type FormDataPrimitive = string | Blob | number | boolean
interface FormDataNest {
[x: string]: FormVal
}
type FormVal = FormDataNest | FormDataPrimitive
Sorry for a late answer, but I was struggling with this as Angular 2 currently does not support file upload. So, the way to do it was sending a XMLHttpRequest with FormData. So, I created a function to do it. I'm using Typescript. To convert it to Javascript just remove data types declaration.
/**
* Transforms the json data into form data.
*
* Example:
*
* Input:
*
* fd = new FormData();
* dob = {
* name: 'phone',
* photos: ['myphoto.jpg', 'myotherphoto.png'],
* price: '615.99',
* color: {
* front: 'red',
* back: 'blue'
* },
* buttons: ['power', 'volup', 'voldown'],
* cameras: [{
* name: 'front',
* res: '5Mpx'
* },{
* name: 'back',
* res: '10Mpx'
* }]
* };
* Say we want to replace 'myotherphoto.png'. We'll have this 'fob'.
* fob = {
* photos: [null, <File object>]
* };
* Say we want to wrap the object (Rails way):
* p = 'product';
*
* Output:
*
* 'fd' object updated. Now it will have these key-values "<key>, <value>":
*
* product[name], phone
* product[photos][], myphoto.jpg
* product[photos][], <File object>
* product[color][front], red
* product[color][back], blue
* product[buttons][], power
* product[buttons][], volup
* product[buttons][], voldown
* product[cameras][][name], front
* product[cameras][][res], 5Mpx
* product[cameras][][name], back
* product[cameras][][res], 10Mpx
*
* #param {FormData} fd FormData object where items will be appended to.
* #param {Object} dob Data object where items will be read from.
* #param {Object = null} fob File object where items will override dob's.
* #param {string = ''} p Prefix. Useful for wrapping objects and necessary for internal use (as this is a recursive method).
*/
append(fd: FormData, dob: Object, fob: Object = null, p: string = ''){
let apnd = this.append;
function isObj(dob, fob, p){
if(typeof dob == "object"){
if(!!dob && dob.constructor === Array){
p += '[]';
for(let i = 0; i < dob.length; i++){
let aux_fob = !!fob ? fob[i] : fob;
isObj(dob[i], aux_fob, p);
}
} else {
apnd(fd, dob, fob, p);
}
} else {
let value = !!fob ? fob : dob;
fd.append(p, value);
}
}
for(let prop in dob){
let aux_p = p == '' ? prop : `${p}[${prop}]`;
let aux_fob = !!fob ? fob[prop] : fob;
isObj(dob[prop], aux_fob, aux_p);
}
}
Here is a short and sweet solution using Object.entries() that will take care of even your nested objects.
// If this is the object you want to convert to FormData...
const item = {
description: 'First item',
price: 13,
photo: File
};
const formData = new FormData();
Object.entries(item).forEach(([key, value]) => {
formData.append(key, value);
});
// At this point, you can then pass formData to your handler method
Read more about Object.entries() over here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
Recursively
const toFormData = (f => f(f))(h => f => f(x => h(h)(f)(x)))(f => fd => pk => d => {
if (d instanceof Object) {
Object.keys(d).forEach(k => {
const v = d[k] === null ? '' : d[k] === true ? 1 : d[k] === false ? 0 : d[k]
if (pk) k = `${pk}[${k}]`
if (v instanceof Object && !(v instanceof Date) && !(v instanceof File)) {
return f(fd)(k)(v)
} else {
fd.append(k, v)
}
})
}
return fd
})(new FormData())()
let data = {
name: 'John',
age: 30,
colors: ['red', 'green', 'blue'],
children: [
{ name: 'Max', age: 3 },
{ name: 'Madonna', age: 10 }
]
}
console.log('data', data)
document.getElementById("data").insertAdjacentHTML('beforeend', JSON.stringify(data))
let formData = toFormData(data)
for (let key of formData.keys()) {
console.log(key, formData.getAll(key).join(','))
document.getElementById("item").insertAdjacentHTML('beforeend', `<li>${key} = ${formData.getAll(key).join(',')}</li>`)
}
<p id="data"></p>
<ul id="item"></ul>
Solution that will handle nested arrays and objects. Somebody may find it useful
function add_form_data(form_data,key,item,arr){
if(typeof(item)==='object' && item && item.constructor===Array){
for(var i=0;i<item.length;i++){
var item2=item[i];
var key2=key+'[' + i +']';
if(arr){
key2=key+'[' + key2 +']';
}
add_form_data(form_data,key2,item2,true);
}
}else if(typeof(item)==='object' && item){
for ( var key2 in item ) {
var item2=item[key2];
if(arr){
key2=key+'[' + key2 +']';
}
add_form_data(form_data,key2,item2,arr);
}
}else{
form_data.append(key,item);
}
}
Usage
var form_data = new FormData();
add_form_data(form_data,null,json_data);// provide json_data here
var string_url_data=new URLSearchParams(form_data).toString();// if query string is needed
In my case my object also had property which was array of files. Since they are binary they should be dealt differently - index doesn't need to be part of the key. So i modified #Vladimir Novopashin's and #developer033's answer:
export function convertToFormData(data, formData, parentKey) {
if(data === null || data === undefined) return null;
formData = formData || new FormData();
if (typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach(key =>
convertToFormData(data[key], formData, (!parentKey ? key : (data[key] instanceof File ? parentKey : `${parentKey}[${key}]`)))
);
} else {
formData.append(parentKey, data);
}
return formData;
}
Maybe you're looking for this, a code that receive your javascript object, create a FormData object from it and then POST it to your server using new Fetch API:
let myJsObj = {'someIndex': 'a value'};
let datos = new FormData();
for (let i in myJsObj){
datos.append( i, myJsObj[i] );
}
fetch('your.php', {
method: 'POST',
body: datos
}).then(response => response.json())
.then(objson => {
console.log('Success:', objson);
})
.catch((error) => {
console.error('Error:', error);
});
I might be a little late to the party but this is what I've created to convert a singular object to FormData.
function formData(formData, filesIgnore = []) {
let data = new FormData();
let files = filesIgnore;
Object.entries(formData).forEach(([key, value]) => {
if (typeof value === 'object' && !files.includes(key)) {
data.append(key, JSON.stringify(value) || null);
} else if (files.includes(key)) {
data.append(key, value[0] || null);
} else {
data.append(key, value || null);
}
})
return data;
}
How does it work?
It will convert and return all properties expect File objects that you've set in the ignore list (2nd argument. If anyone could tell me a better way to determine this that would help!) into a json string using JSON.stringify. Then on your server you'll just need to convert it back into a JSON object.
Example:
let form = {
first_name: 'John',
last_name: 'Doe',
details: {
phone_number: 1234 5678 910,
address: '123 Some Street',
},
profile_picture: [object FileList] // set by your form file input. Currently only support 1 file per property.
}
function submit() {
let data = formData(form, ['profile_picture']);
axios.post('/url', data).then(res => {
console.log('object uploaded');
})
}
I am still kinda new to Http requests and JavaScript so any feedback would be highly appreciated!
Note: This answer doesn't answer the question directly, it gives illustrations and alternatives instead.
People usually use FormData to allow them to upload files, there are 3 ways to do that, I'll mention them in detail in this answer.
1. Get the data from the form directly
To do so, you need to have the name and the value attributes in each input, then you tell the FormData to get the values from the form DOM;
let formData = new FormData(data.target as HTMLFormElement)
Pros:
you don't need to involve a JS solution to get the values.
native support for nested data, arrays, etc ...
cons:
you will need to make sure that all data needed are added as a value attribute on input DOMs
2. Converting data by JS
If you have the values stored in a variable then you can append them to the FormData using JS.
Pros:
can be manipulated as needed
you don't need to have value attributes in input DOMs
Cons:
custom complex JS to append nested data to the FormData object
you need to have the values stored in a variable
3. Upload files in a separate request
you can make a separate form for the file upload input.
Pros:
you don't need to wait for uploads to submit
you don't need to worry about appending all your data to the FormData object, you will only append the files.
Cons:
you need to handle these uploads in the backend before creating the entity itself
you need to handle deleting a file, both on front-end and back-end
Using jquery you can do this simply by $.param(obj) .
Example:
const obj = {
description: 'Some Item',
price: '0.00',
srate: '0.00',
color: 'red'
}
const form_obj = $.param(obj);
$.ajax({
url:"example.com",
method:"POST",
data:form_obj
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
This method convert a JS object to a FormData :
function convertToFormData(params) {
return Object.entries(params)
.reduce((acc, [key, value]) => {
if (Array.isArray(value)) {
value.forEach((v, k) => acc.append(`${key}[${k}]`, value));
} else if (typeof value === 'object' && !(value instanceof File) && !(value instanceof Date)) {
Object.entries(value).forEach((v, k) => acc.append(`${key}[${k}]`, value));
} else {
acc.append(key, value);
}
return acc;
}, new FormData());
}
You can simply install qs:
npm i qs
Simply import:
import qs from 'qs'
Pass object to qs.stringify():
var item = {
description: 'Some Item',
price : '0.00',
srate : '0.00',
color : 'red',
...
...
}
qs.stringify(item)
I used this for Post my object data as Form Data.
const encodeData = require('querystring');
const object = {type: 'Authorization', username: 'test', password: '123456'};
console.log(object);
console.log(encodeData.stringify(object));
I reference this from Gudradain's answer. I edit it a little in Typescript format.
class UtilityService {
private appendFormData(formData, data, rootName) {
let root = rootName || '';
if (data instanceof File) {
formData.append(root, data);
} else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
this.appendFormData(formData, data[i], root + '[' + i + ']');
}
} else if (typeof data === 'object' && data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (root === '') {
this.appendFormData(formData, data[key], key);
} else {
this.appendFormData(formData, data[key], root + '.' + key);
}
}
}
} else {
if (data !== null && typeof data !== 'undefined') {
formData.append(root, data);
}
}
}
getFormDataFromObj(data) {
var formData = new FormData();
this.appendFormData(formData, data, '');
return formData;
}
}
export let UtilityMan = new UtilityService();
Simply this can be done :
var item: { some1: "ajbd" , some2: "dds".. }
let myFormData = new FormData();
const abc = item.some1;
const xyz = item.some2;
myFormData.append('field1', abc);
myFormData.append('field2', xyz);
fetch('http:url', {
method: 'POST',
headers: {
'Content-Type': false,
},
body: myFormData,
}).
do promise ..
Here is a very simplistic TypeScript implementation based on answers from #Vladimir Novopashin and #developer033. TypeScript Playground
type Serializeable =
| string
| number
| boolean
| null
| Date
| File
| { [x: string | number]: Serializeable }
| Array<Serializeable>;
function serialize(
data: Serializeable,
parentKey = '',
formData: FormData = new FormData()
): FormData {
if ( typeof data === 'string') {
formData.append(parentKey, data);
} else if ( typeof data === 'number') {
formData.append(parentKey, data.toString());
} else if ( typeof data === 'boolean') {
formData.append(parentKey, data ? 'true' : 'false');
} else if (data === null) {
formData.append(parentKey, 'null');
} else if (data instanceof Date) {
formData.append(parentKey, data.toISOString());
} else if (data instanceof File) {
formData.append(parentKey, data);
} else {
// Arrays and objects
Object.entries(data).forEach((entry: [string | number, Serializeable]) => {
const [key, value] = entry;
serialize(value, parentKey ? `${parentKey}[${key}]` : key.toString(), formData);
});
}
return formData;
}
Try obj2fd => https://www.npmjs.com/package/obj2fd
import obj2fd from 'obj2fd'
let data = {a:1, b:2, c:{ca:1}};
let dataWithFormData = obj2fd(data);
//result => [a=>1, b=>2, c=>[ca=>1]]
Related
How can I can convert my JS Object to FormData?
The reason why I want to do this is, I have an object that I constructed out of the ~100 form field values.
var item = {
description: 'Some Item',
price : '0.00',
srate : '0.00',
color : 'red',
...
...
}
Now I am asked to add the upload file functionality to my form which, of-course is impossible via JSON and so I am planning on moving to FormData. So is there any way that I can convert my JS object to FormData?
If you have an object, you can easily create a FormData object and append the names and values from that object to formData.
You haven't posted any code, so it's a general example;
var form_data = new FormData();
for ( var key in item ) {
form_data.append(key, item[key]);
}
$.ajax({
url : 'http://example.com/upload.php',
data : form_data,
processData : false,
contentType : false,
type: 'POST'
}).done(function(data){
// do stuff
});
There are more examples in the documentation on MDN
With ES6 and a more functional programming approach #adeneo's answer could looks like this:
function getFormData(object) {
const formData = new FormData();
Object.keys(object).forEach(key => formData.append(key, object[key]));
return formData;
}
And alternatively using .reduce() and arrow-functions:
const getFormData = object => Object.keys(object).reduce((formData, key) => {
formData.append(key, object[key]);
return formData;
}, new FormData());
This function adds all data from object to FormData
ES6 version from #developer033:
function buildFormData(formData, data, parentKey) {
if (data && typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach(key => {
buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key);
});
} else {
const value = data == null ? '' : data;
formData.append(parentKey, value);
}
}
function jsonToFormData(data) {
const formData = new FormData();
buildFormData(formData, data);
return formData;
}
const my_data = {
num: 1,
falseBool: false,
trueBool: true,
empty: '',
und: undefined,
nullable: null,
date: new Date(),
name: 'str',
another_object: {
name: 'my_name',
value: 'whatever'
},
array: [
{
key1: {
name: 'key1'
}
}
]
};
jsonToFormData(my_data)
jQuery version:
function appendFormdata(FormData, data, name){
name = name || '';
if (typeof data === 'object'){
$.each(data, function(index, value){
if (name == ''){
appendFormdata(FormData, value, index);
} else {
appendFormdata(FormData, value, name + '['+index+']');
}
})
} else {
FormData.append(name, data);
}
}
var formData = new FormData(),
your_object = {
name: 'test object',
another_object: {
name: 'and other objects',
value: 'whatever'
}
};
appendFormdata(formData, your_object);
Try JSON.stringify function as below
var postData = JSON.stringify(item);
var formData = new FormData();
formData.append("postData",postData );
The other answers were incomplete for me. I started from #Vladimir Novopashin answer and modified it. Here are the things, that I needed and bug I found:
Support for file
Support for array
Bug: File inside complex object needs to be added with .prop instead of [prop]. For example, formData.append('photos[0][file]', file) didn't work on google chrome, while
formData.append('photos[0].file', file) worked
Ignore some properties in my object
The following code should work on IE11 and evergreen browsers.
function objectToFormData(obj, rootName, ignoreList) {
var formData = new FormData();
function appendFormData(data, root) {
if (!ignore(root)) {
root = root || '';
if (data instanceof File) {
formData.append(root, data);
} else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
appendFormData(data[i], root + '[' + i + ']');
}
} else if (typeof data === 'object' && data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (root === '') {
appendFormData(data[key], key);
} else {
appendFormData(data[key], root + '.' + key);
}
}
}
} else {
if (data !== null && typeof data !== 'undefined') {
formData.append(root, data);
}
}
}
}
function ignore(root){
return Array.isArray(ignoreList)
&& ignoreList.some(function(x) { return x === root; });
}
appendFormData(obj, rootName);
return formData;
}
function toFormData(o) {
return Object.entries(o).reduce((d,e) => (d.append(...e),d), new FormData())
}
var object = {
username: 'JohnDoe',
file: new File(['foo'], 'foo.txt', {type: 'text/plain'})
}
fetch('https://httpbin.org/post', {
method: 'POST',
body: toFormData(object)
}).then(r => r.json()).then(console.log)
I had a scenario where nested JSON had to be serialised in a linear fashion while form data is constructed, since this is how server expects values. So, I wrote a small recursive function which translates the JSON which is like this:
{
"orderPrice":"11",
"cardNumber":"************1234",
"id":"8796191359018",
"accountHolderName":"Raj Pawan",
"expiryMonth":"02",
"expiryYear":"2019",
"issueNumber":null,
"billingAddress":{
"city":"Wonderland",
"code":"8796682911767",
"firstname":"Raj Pawan",
"lastname":"Gumdal",
"line1":"Addr Line 1",
"line2":null,
"state":"US-AS",
"region":{
"isocode":"US-AS"
},
"zip":"76767-6776"
}
}
Into something like this:
{
"orderPrice":"11",
"cardNumber":"************1234",
"id":"8796191359018",
"accountHolderName":"Raj Pawan",
"expiryMonth":"02",
"expiryYear":"2019",
"issueNumber":null,
"billingAddress.city":"Wonderland",
"billingAddress.code":"8796682911767",
"billingAddress.firstname":"Raj Pawan",
"billingAddress.lastname":"Gumdal",
"billingAddress.line1":"Addr Line 1",
"billingAddress.line2":null,
"billingAddress.state":"US-AS",
"billingAddress.region.isocode":"US-AS",
"billingAddress.zip":"76767-6776"
}
The server would accept form data which is in this converted format.
Here is the function:
function jsonToFormData (inJSON, inTestJSON, inFormData, parentKey) {
// http://stackoverflow.com/a/22783314/260665
// Raj: Converts any nested JSON to formData.
var form_data = inFormData || new FormData();
var testJSON = inTestJSON || {};
for ( var key in inJSON ) {
// 1. If it is a recursion, then key has to be constructed like "parent.child" where parent JSON contains a child JSON
// 2. Perform append data only if the value for key is not a JSON, recurse otherwise!
var constructedKey = key;
if (parentKey) {
constructedKey = parentKey + "." + key;
}
var value = inJSON[key];
if (value && value.constructor === {}.constructor) {
// This is a JSON, we now need to recurse!
jsonToFormData (value, testJSON, form_data, constructedKey);
} else {
form_data.append(constructedKey, inJSON[key]);
testJSON[constructedKey] = inJSON[key];
}
}
return form_data;
}
Invocation:
var testJSON = {};
var form_data = jsonToFormData (jsonForPost, testJSON);
I am using testJSON just to see the converted results since I would not be able to extract the contents of form_data. AJAX post call:
$.ajax({
type: "POST",
url: somePostURL,
data: form_data,
processData : false,
contentType : false,
success: function (data) {
},
error: function (e) {
}
});
You can simply use:
formData.append('item', JSON.stringify(item));
2022 Update
Axios now supports automatic serialization of an object to FormData
Since version 0.27 Axios supports automatic object serialization to a FormData object if the request Content-Type header is set to multipart/form-data. Read more
Nested objects and files
Following solutions handles nested objects and arrays and files.
const buildFormData = (formData: FormData, data: FormVal, parentKey?: string) => {
if (Array.isArray(data)) {
data.forEach((el) => {
buildFormData(formData, el, parentKey)
})
} else if (typeof data === "object" && !(data instanceof File)) {
Object.keys(data).forEach((key) => {
buildFormData(formData, (data as FormDataNest)[key], parentKey ? `${parentKey}.${key}` : key)
})
} else {
if (isNil(data)) {
return
}
let value = typeof data === "boolean" || typeof data === "number" ? data.toString() : data
formData.append(parentKey as string, value)
}
}
export const getFormData = (data: Record<string, FormDataNest>) => {
const formData = new FormData()
buildFormData(formData, data)
return formData
}
Types
type FormDataPrimitive = string | Blob | number | boolean
interface FormDataNest {
[x: string]: FormVal
}
type FormVal = FormDataNest | FormDataPrimitive
Sorry for a late answer, but I was struggling with this as Angular 2 currently does not support file upload. So, the way to do it was sending a XMLHttpRequest with FormData. So, I created a function to do it. I'm using Typescript. To convert it to Javascript just remove data types declaration.
/**
* Transforms the json data into form data.
*
* Example:
*
* Input:
*
* fd = new FormData();
* dob = {
* name: 'phone',
* photos: ['myphoto.jpg', 'myotherphoto.png'],
* price: '615.99',
* color: {
* front: 'red',
* back: 'blue'
* },
* buttons: ['power', 'volup', 'voldown'],
* cameras: [{
* name: 'front',
* res: '5Mpx'
* },{
* name: 'back',
* res: '10Mpx'
* }]
* };
* Say we want to replace 'myotherphoto.png'. We'll have this 'fob'.
* fob = {
* photos: [null, <File object>]
* };
* Say we want to wrap the object (Rails way):
* p = 'product';
*
* Output:
*
* 'fd' object updated. Now it will have these key-values "<key>, <value>":
*
* product[name], phone
* product[photos][], myphoto.jpg
* product[photos][], <File object>
* product[color][front], red
* product[color][back], blue
* product[buttons][], power
* product[buttons][], volup
* product[buttons][], voldown
* product[cameras][][name], front
* product[cameras][][res], 5Mpx
* product[cameras][][name], back
* product[cameras][][res], 10Mpx
*
* #param {FormData} fd FormData object where items will be appended to.
* #param {Object} dob Data object where items will be read from.
* #param {Object = null} fob File object where items will override dob's.
* #param {string = ''} p Prefix. Useful for wrapping objects and necessary for internal use (as this is a recursive method).
*/
append(fd: FormData, dob: Object, fob: Object = null, p: string = ''){
let apnd = this.append;
function isObj(dob, fob, p){
if(typeof dob == "object"){
if(!!dob && dob.constructor === Array){
p += '[]';
for(let i = 0; i < dob.length; i++){
let aux_fob = !!fob ? fob[i] : fob;
isObj(dob[i], aux_fob, p);
}
} else {
apnd(fd, dob, fob, p);
}
} else {
let value = !!fob ? fob : dob;
fd.append(p, value);
}
}
for(let prop in dob){
let aux_p = p == '' ? prop : `${p}[${prop}]`;
let aux_fob = !!fob ? fob[prop] : fob;
isObj(dob[prop], aux_fob, aux_p);
}
}
Here is a short and sweet solution using Object.entries() that will take care of even your nested objects.
// If this is the object you want to convert to FormData...
const item = {
description: 'First item',
price: 13,
photo: File
};
const formData = new FormData();
Object.entries(item).forEach(([key, value]) => {
formData.append(key, value);
});
// At this point, you can then pass formData to your handler method
Read more about Object.entries() over here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
Recursively
const toFormData = (f => f(f))(h => f => f(x => h(h)(f)(x)))(f => fd => pk => d => {
if (d instanceof Object) {
Object.keys(d).forEach(k => {
const v = d[k] === null ? '' : d[k] === true ? 1 : d[k] === false ? 0 : d[k]
if (pk) k = `${pk}[${k}]`
if (v instanceof Object && !(v instanceof Date) && !(v instanceof File)) {
return f(fd)(k)(v)
} else {
fd.append(k, v)
}
})
}
return fd
})(new FormData())()
let data = {
name: 'John',
age: 30,
colors: ['red', 'green', 'blue'],
children: [
{ name: 'Max', age: 3 },
{ name: 'Madonna', age: 10 }
]
}
console.log('data', data)
document.getElementById("data").insertAdjacentHTML('beforeend', JSON.stringify(data))
let formData = toFormData(data)
for (let key of formData.keys()) {
console.log(key, formData.getAll(key).join(','))
document.getElementById("item").insertAdjacentHTML('beforeend', `<li>${key} = ${formData.getAll(key).join(',')}</li>`)
}
<p id="data"></p>
<ul id="item"></ul>
Solution that will handle nested arrays and objects. Somebody may find it useful
function add_form_data(form_data,key,item,arr){
if(typeof(item)==='object' && item && item.constructor===Array){
for(var i=0;i<item.length;i++){
var item2=item[i];
var key2=key+'[' + i +']';
if(arr){
key2=key+'[' + key2 +']';
}
add_form_data(form_data,key2,item2,true);
}
}else if(typeof(item)==='object' && item){
for ( var key2 in item ) {
var item2=item[key2];
if(arr){
key2=key+'[' + key2 +']';
}
add_form_data(form_data,key2,item2,arr);
}
}else{
form_data.append(key,item);
}
}
Usage
var form_data = new FormData();
add_form_data(form_data,null,json_data);// provide json_data here
var string_url_data=new URLSearchParams(form_data).toString();// if query string is needed
In my case my object also had property which was array of files. Since they are binary they should be dealt differently - index doesn't need to be part of the key. So i modified #Vladimir Novopashin's and #developer033's answer:
export function convertToFormData(data, formData, parentKey) {
if(data === null || data === undefined) return null;
formData = formData || new FormData();
if (typeof data === 'object' && !(data instanceof Date) && !(data instanceof File)) {
Object.keys(data).forEach(key =>
convertToFormData(data[key], formData, (!parentKey ? key : (data[key] instanceof File ? parentKey : `${parentKey}[${key}]`)))
);
} else {
formData.append(parentKey, data);
}
return formData;
}
Maybe you're looking for this, a code that receive your javascript object, create a FormData object from it and then POST it to your server using new Fetch API:
let myJsObj = {'someIndex': 'a value'};
let datos = new FormData();
for (let i in myJsObj){
datos.append( i, myJsObj[i] );
}
fetch('your.php', {
method: 'POST',
body: datos
}).then(response => response.json())
.then(objson => {
console.log('Success:', objson);
})
.catch((error) => {
console.error('Error:', error);
});
I might be a little late to the party but this is what I've created to convert a singular object to FormData.
function formData(formData, filesIgnore = []) {
let data = new FormData();
let files = filesIgnore;
Object.entries(formData).forEach(([key, value]) => {
if (typeof value === 'object' && !files.includes(key)) {
data.append(key, JSON.stringify(value) || null);
} else if (files.includes(key)) {
data.append(key, value[0] || null);
} else {
data.append(key, value || null);
}
})
return data;
}
How does it work?
It will convert and return all properties expect File objects that you've set in the ignore list (2nd argument. If anyone could tell me a better way to determine this that would help!) into a json string using JSON.stringify. Then on your server you'll just need to convert it back into a JSON object.
Example:
let form = {
first_name: 'John',
last_name: 'Doe',
details: {
phone_number: 1234 5678 910,
address: '123 Some Street',
},
profile_picture: [object FileList] // set by your form file input. Currently only support 1 file per property.
}
function submit() {
let data = formData(form, ['profile_picture']);
axios.post('/url', data).then(res => {
console.log('object uploaded');
})
}
I am still kinda new to Http requests and JavaScript so any feedback would be highly appreciated!
Note: This answer doesn't answer the question directly, it gives illustrations and alternatives instead.
People usually use FormData to allow them to upload files, there are 3 ways to do that, I'll mention them in detail in this answer.
1. Get the data from the form directly
To do so, you need to have the name and the value attributes in each input, then you tell the FormData to get the values from the form DOM;
let formData = new FormData(data.target as HTMLFormElement)
Pros:
you don't need to involve a JS solution to get the values.
native support for nested data, arrays, etc ...
cons:
you will need to make sure that all data needed are added as a value attribute on input DOMs
2. Converting data by JS
If you have the values stored in a variable then you can append them to the FormData using JS.
Pros:
can be manipulated as needed
you don't need to have value attributes in input DOMs
Cons:
custom complex JS to append nested data to the FormData object
you need to have the values stored in a variable
3. Upload files in a separate request
you can make a separate form for the file upload input.
Pros:
you don't need to wait for uploads to submit
you don't need to worry about appending all your data to the FormData object, you will only append the files.
Cons:
you need to handle these uploads in the backend before creating the entity itself
you need to handle deleting a file, both on front-end and back-end
Using jquery you can do this simply by $.param(obj) .
Example:
const obj = {
description: 'Some Item',
price: '0.00',
srate: '0.00',
color: 'red'
}
const form_obj = $.param(obj);
$.ajax({
url:"example.com",
method:"POST",
data:form_obj
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
This method convert a JS object to a FormData :
function convertToFormData(params) {
return Object.entries(params)
.reduce((acc, [key, value]) => {
if (Array.isArray(value)) {
value.forEach((v, k) => acc.append(`${key}[${k}]`, value));
} else if (typeof value === 'object' && !(value instanceof File) && !(value instanceof Date)) {
Object.entries(value).forEach((v, k) => acc.append(`${key}[${k}]`, value));
} else {
acc.append(key, value);
}
return acc;
}, new FormData());
}
You can simply install qs:
npm i qs
Simply import:
import qs from 'qs'
Pass object to qs.stringify():
var item = {
description: 'Some Item',
price : '0.00',
srate : '0.00',
color : 'red',
...
...
}
qs.stringify(item)
I used this for Post my object data as Form Data.
const encodeData = require('querystring');
const object = {type: 'Authorization', username: 'test', password: '123456'};
console.log(object);
console.log(encodeData.stringify(object));
I reference this from Gudradain's answer. I edit it a little in Typescript format.
class UtilityService {
private appendFormData(formData, data, rootName) {
let root = rootName || '';
if (data instanceof File) {
formData.append(root, data);
} else if (Array.isArray(data)) {
for (var i = 0; i < data.length; i++) {
this.appendFormData(formData, data[i], root + '[' + i + ']');
}
} else if (typeof data === 'object' && data) {
for (var key in data) {
if (data.hasOwnProperty(key)) {
if (root === '') {
this.appendFormData(formData, data[key], key);
} else {
this.appendFormData(formData, data[key], root + '.' + key);
}
}
}
} else {
if (data !== null && typeof data !== 'undefined') {
formData.append(root, data);
}
}
}
getFormDataFromObj(data) {
var formData = new FormData();
this.appendFormData(formData, data, '');
return formData;
}
}
export let UtilityMan = new UtilityService();
Simply this can be done :
var item: { some1: "ajbd" , some2: "dds".. }
let myFormData = new FormData();
const abc = item.some1;
const xyz = item.some2;
myFormData.append('field1', abc);
myFormData.append('field2', xyz);
fetch('http:url', {
method: 'POST',
headers: {
'Content-Type': false,
},
body: myFormData,
}).
do promise ..
Here is a very simplistic TypeScript implementation based on answers from #Vladimir Novopashin and #developer033. TypeScript Playground
type Serializeable =
| string
| number
| boolean
| null
| Date
| File
| { [x: string | number]: Serializeable }
| Array<Serializeable>;
function serialize(
data: Serializeable,
parentKey = '',
formData: FormData = new FormData()
): FormData {
if ( typeof data === 'string') {
formData.append(parentKey, data);
} else if ( typeof data === 'number') {
formData.append(parentKey, data.toString());
} else if ( typeof data === 'boolean') {
formData.append(parentKey, data ? 'true' : 'false');
} else if (data === null) {
formData.append(parentKey, 'null');
} else if (data instanceof Date) {
formData.append(parentKey, data.toISOString());
} else if (data instanceof File) {
formData.append(parentKey, data);
} else {
// Arrays and objects
Object.entries(data).forEach((entry: [string | number, Serializeable]) => {
const [key, value] = entry;
serialize(value, parentKey ? `${parentKey}[${key}]` : key.toString(), formData);
});
}
return formData;
}
Try obj2fd => https://www.npmjs.com/package/obj2fd
import obj2fd from 'obj2fd'
let data = {a:1, b:2, c:{ca:1}};
let dataWithFormData = obj2fd(data);
//result => [a=>1, b=>2, c=>[ca=>1]]
I am trying to get the value feature_1 of a key name from the array data and set feature_1 as the key of another array asset which has an array as value.
Example :
//input:
data: {
name: "feature_1",
value_a: 1,
value_b: 2
}
//Output:
asset: {
feature_1:[1,2]
}
You can try:
var asset = {};
if ('name' in data) {
var tmp = [];
for (k in data){
if (k != 'name') tmp.push(data[k]);
}
asset[data['name']] = tmp;
}
and in case your interpreter supports ES6 you can use, for example:
let {name, ...v} = data;
let asset = {[name]: Object.values(v)};
If supports same keys in a JSON object ( It seems not ) You can do it like this:
let data= {
name: "feature_1",
value_a: 1,
value_b: 2,
value_x: 500,
name: "feature_2",
value_a: 17,
value_b: 21,
value_x: 510
}
console.log(
Object.entries(data).reduce((a,[key,value],index)=>{
if (key==='name')
return {index, assets: {...a.assets, [value]:[]}};
return {
index : a.index,
assets : {
...a.assets,
[Object.entries(a.assets)[a.index][0]] : [...Object.entries(a.assets)[a.index][1],value]
}};
},{index:0,assets:{}}).assets
);
But we know the correct way is using separated array rows.
This can be accomplished the following way:
const obj = {
data: {
name: "feature_1",
value_a: 1,
value_b: 2
}
};
const output = {
assets: {
[obj.data.name]: Object.values(obj.data).filter(el => el !== obj.data.name)
}
}
console.log(output);
Is it possible to append nested object to FormData?
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
$.each(data, function(key, value) {
formData.append(key, value);
});
Server console - console.log(req.body)
{
title: 'title',
text: 'text',
preview: '[object Object]'
}
How can I get the exact value of preview: {p_title:'p title', p_text: 'p text'}?
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
for(let dataKey in data) {
if(dataKey === 'preview') {
// append nested object
for (let previewKey in data[dataKey]) {
formData.append(`preview[${previewKey}]`, data[dataKey][previewKey]);
}
}
else {
formData.append(dataKey, data[dataKey]);
}
}
Console formData
for (let val of formData.entries()) {
console.log(val[0]+ ', ' + val[1]);
}
To append an object to formData, you need to stringify it first, like this:
let objToAppend= {
key1: value1,
key2: value2,
}
let formData = new FormData();
formData.append('obj', JSON.stringify(objToAppend));
Then on the server side to access it you need to parse it first using JSON.parse().
Hope it helps!
First of all I apologize for my bad English.
I did something to get the data properly on the server side, not when I was writing to the console. I hope you want to do this.
I had to write a javascript function to get the client side nested data on the server side.
For this I wrote the obj2FormData() function. Square brackets seem to work.
function obj2FormData(obj, formData = new FormData()){
this.formData = formData;
this.createFormData = function(obj, subKeyStr = ''){
for(let i in obj){
let value = obj[i];
let subKeyStrTrans = subKeyStr ? subKeyStr + '[' + i + ']' : i;
if(typeof(value) === 'string' || typeof(value) === 'number'){
this.formData.append(subKeyStrTrans, value);
} else if(typeof(value) === 'object'){
this.createFormData(value, subKeyStrTrans);
}
}
}
this.createFormData(obj);
return this.formData;
}
When sending data with Ajax, we convert the nested object to the FormData object.
let formData = obj2FormData({
name : 'Emrah',
surname : 'Tuncel',
birth: 1983,
child : {
name: 'Eylul',
surname: 'Tuncel',
toys: ['ball', 'baby']
},
color: ['red', 'yellow']
});
Now let's send the FormData that we transformed with ajax.
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', response => {
//AJAX RESPONSE >>
console.log(response);
//AJAX RESPONSE //
});
xhr.open('POST','response.php');
xhr.send(formData);
When I pressed the data to the screen with PHP, I got the exact result I wanted. It doesn't matter whether the method is POST or GET.
response.php
<pre><? print_r($_GET) ?></pre>
<pre><? print_r($_POST) ?></pre>
The output was as follows.
Array
(
[name] => Emrah
[surname] => Tuncel
[birth] => 1983
[child] => Array
(
[name] => Eylul
[surname] => Tuncel
[toys] => Array
(
[0] => ball
[1] => baby
)
)
[color] => Array
(
[0] => red
[1] => yellow
)
)
Hopefully it benefits your business.
FormData values are automatically converted to string. You can try to do it using Blob.
Or just put it as string using JSON.stringify(obj).
$.each(data, function(key, value){
if (typeof(value) === 'object') {
value = new Blob([JSON.stringify(value)], {type : 'application/json'});// or just JSON.stringify(value)
}
formData.append(key, value);
});
Here is a "A convenient JavaScript function that converts an object to a FormData instance" github, also available as a npm package, very simple to use
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
var formData = objectToFormData(data);
This for me do the work:
use . to separate key and subKey
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
for(let key in data) {
if(typeof(data[key]) === 'object') {
for (let subKey in data[key]) {
formData.append(`${key}.${subKey}`, data[key][subKey]);
}
}
else {
formData.append(key, data[key]);
}
}
Try out object-to-formdata. It's a convenient JavaScript function that converts Objects to FormData instances.
import { objectToFormData } from 'object-to-formdata';
const object = {
/**
* key-value mapping
* values can be primitives or objects
*/
};
const options = {
/**
* include array indices in FormData keys
* defaults to false
*/
indices: false,
/**
* treat null values like undefined values and ignore them
* defaults to false
*/
nullsAsUndefineds: false,
/**
* convert true or false to 1 or 0 respectively
* defaults to false
*/
booleansAsIntegers: false,
};
const formData = objectToFormData(
object,
options, // optional
existingFormData, // optional
keyPrefix, // optional
);
console.log(formData);
You don't need to use any third party modules and JSON.stringify() isn't ideal if you have nested objects. Instead you can use Object.entries().
// If this is the object you want to convert to FormData...
const item = {
description: 'First item',
price: 13,
photo: File
};
const formData = new FormData();
Object.entries(item).forEach(([key, value]) => {
formData.append(key, value);
});
// At this point, you can then pass formData to your handler method
Read more about Object.entries() over here - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
use hashName[keyName]
let formData = new FormData();
let data = {
title: 'title',
text: 'text',
preview: {p_title:'p title', p_text: 'p text'}
};
for(let key in data) {
if(typeof(data[key]) === 'object') {
for (let subKey in data[key]) {
formData.append('${key}[${subKey}]', data[key][subKey]);
}
}
else {
formData.append(key, data[key]);
}
}
I hope I can help, I did it in a simpler way and I believe it works for all hypotheses, please test:
const parses = []
const fKey = key => ((function until(value, comp = value) {
const result = value.replace(/\.([A-z0-9]*)(\.|$)/, '[$1]$2')
return comp !== result ? until(result, value) : result
})(key))
function populateFormData(values, form = new FormData(), base = '') {
Object.keys(values).forEach(key => {
const value = values[key]
if (typeof value == 'string' ||
typeof value == 'number' ||
typeof value == 'boolean')
{
form.append(fKey(`${base}${key}`), value)
parses.push({
key: `${fKey(`${base}${key}`)}`,
value
})
}
else if (typeof value == 'object')
{
populateFormData(value, form, `${base}${key}.`)
}
})
return form;
}
populateFormData({
title: 'Lucas',
text: 'Is good :)',
preview: {
p_title: 'I am a P title',
p_text: 'I am a P text',
test: {
example: 2,
my: {
obj: [
'eba',
{
hyper: 'text'
},
123
],
yes: true
}
}
}
})
console.log(parses)
I guess #Emrah Tuncel's solution is really fine, maybe with some improvement:
function object_to_form_data(data,formData,index = '') {
for (const objKey in data) {
const currentValue = data[objKey];
const currentIndex = index ? `${index}[${objKey}]` : objKey;
// Verify that currentValue is not an array, as arrays are also objects in js
if (typeof currentValue === 'object' && !Array.isArray(currentValue)) {
// If the currently iterated object is an empty object, we must not append it to the
// formData, as that one will get appended as empty JSON object string too. In that case, as even null will
// get parsed to "null" with formData, add a 'none' string as the value, and handle the
// respective cases on the server side
if (Object.keys(currentValue).length === 0) {
formData.append(currentIndex,'none'); // You may change this to however you wanna handle empty objects
continue;
}
object_to_form_data(currentValue,formData,currentIndex);
} else {
formData.append(currentIndex,currentValue);
}
}
}
The main thing was that you must consider arrays and handle their special case, as they also enter the "typeof === 'object'" loop in javascript. You should also handle the special case where you have an empty object value within your nested object. Now, at least for my use-cases, it's fully compatible with my object-validations and sanitizations on the server-side.
I think it can be a good solution for you:
function getFormData (obj = {}, formData = new FormData(), key = '') {
if (!([Array, File, Object].includes(obj.constructor))) {
return formData;
}
// Handle File recursive
if (obj instanceof File) {
formData.append(key, obj);
return formData;
}
for (const prop in obj) {
// Validate value type
if (obj[prop] && !([String, Number, Boolean, Array, Object, File].includes(obj[prop].constructor))) {
continue;
}
// Set deep index of prop
const deepKey = key ? key + `[${prop}]` : prop;
// Handle array
if (Array.isArray(obj[prop])) {
obj[prop].forEach((item, index) => {
getFormData(item, formData, `${deepKey}[${index}]`);
})
continue;
}
(typeof obj[prop] === 'object' && obj[prop] && obj[prop].constructor === Object)
? getFormData(obj[prop], formData, deepKey) // Handle object
: formData.append(deepKey, [undefined, null].includes(obj[prop]) ? '' : obj[prop]) // Handle string, number, boolean
}
return formData;
}
const data = {
string: 'abcd...',
number: 1234,
boolean: true,
boolean2: false,
object: {
index: 1,
value: 'value',
boolean3: false,
},
array: [
{
index: 2,
value: 'value 2'
},
{
index: 3,
value: 'value 3'
},
[
{
index: 4,
value: 'value 2'
},
{
index: 5,
value: 'value 3',
boolean4: false,
null: null,
function: (x,y) => { return x + y; },
},
true,
undefined,
(x,y) => { return x + y; },
new File(["woo"], 'woo.txt')
],
false,
new File(["foo"], 'file.txt')
],
};
const formData = getFormData(data);
for (let pair of formData.entries()) {
console.log(pair[0]+ ', ' + pair[1]);
}
I have below method where i get value from the nested object , want to refactor it to best possible way but cannot use Object.hasOwnProperty because of linting rules
export const getValueFromNestedObject = (obj = {}, key = '') => {
let result;
for (let i = 0; i< Object.keys(obj).length; i++) {
if (typeof obj[Object.keys(obj)[i]] === 'object') {
result = getValueFromNestedObject(obj[Object.keys(obj)[i]], key);
if(typeof result != 'undefined') {
return result;
}
} else if (Object.keys(obj)[i] === key) {
return obj[key];
}
}
}
const valueObj = {
'#close' : {
'type': 'button',
'dom' : {
'data-linkName' : 'Close'
}
},
'#popupText': {
'type': 'HTML',
'options': {
'html': 'Pop Up Text that is required as result'
'label': 'Heads Up'
}
}
};
const key = 'html';
console.log(getValueFromNestedObject(valueObj, key));
expect(getValueFromNestedObject(valueObj, key)).toEqual('Pop Up Text that is required as result')
I have a javascript object width depth.
I need to know the exact path from this key within the object ex: "obj1.obj2.data1"
I already know the key is data1, the value is 123.
My javascript object look like this
{
obj1: {
obj2: {
data1: 213,
data2: "1231",
obj3: {
data: "milf"
}
}
},
obj4: {
description: "toto"
}
}
How could I achieve that ?
here is a jsfiddle : http://jsfiddle.net/3hvav8xf/8/
I am trying to implement getPath.
I think recursive function can help to you (Updated version, to check value)
function path(c, name, v, currentPath, t){
var currentPath = currentPath || "root";
for(var i in c){
if(i == name && c[i] == v){
t = currentPath;
}
else if(typeof c[i] == "object"){
return path(c[i], name, v, currentPath + "." + i);
}
}
return t + "." + name;
};
console.log(path({1: 2, s: 5, 2: {3: {2: {s: 1, p: 2}}}}, "s", 1));
The following finds the path in any level of nested objects. Also with arrays.
It returns all the paths found, which is something you want if you have keys with the same name.
I like this approach because it works with lodash methods get and set out-of-the-box.
function findPathsToKey(options) {
let results = [];
(function findKey({
key,
obj,
pathToKey,
}) {
const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
if (obj.hasOwnProperty(key)) {
results.push(`${oldPath}${key}`);
return;
}
if (obj !== null && typeof obj === "object" && !Array.isArray(obj)) {
for (const k in obj) {
if (obj.hasOwnProperty(k)) {
if (Array.isArray(obj[k])) {
for (let j = 0; j < obj[k].length; j++) {
findKey({
obj: obj[k][j],
key,
pathToKey: `${oldPath}${k}[${j}]`,
});
}
}
if (obj[k] !== null && typeof obj[k] === "object") {
findKey({
obj: obj[k],
key,
pathToKey: `${oldPath}${k}`,
});
}
}
}
}
})(options);
return results;
}
findPathsToKey({ obj: objWithDuplicates, key: "d" })
// ["parentKey.arr[0].c.d", "parentKey.arr[1].c.d", "parentKey.arr[2].c.d"]
Try it here - https://jsfiddle.net/spuhb8v7/1/
If you want the result to be a single key (first encountered), you can change the results to be a string and if defined, then return the function with it.
I ended up with the following function, that works with nested objects/arrays :
function findPath (obj, name, val, currentPath) {
currentPath = currentPath || ''
let matchingPath
if (!obj || typeof obj !== 'object') return
if (obj[name] === val) return `${currentPath}['${name}']`
for (const key of Object.keys(obj)) {
if (key === name && obj[key] === val) {
matchingPath = currentPath
} else {
matchingPath = findPath(obj[key], name, val, `${currentPath}['${key}']`)
}
if (matchingPath) break
}
return matchingPath
}
const treeData = [{
id: 1,
children: [{
id: 2
}]
}, {
id: 3,
children: [{
id: 4,
children: [{
id: 5
}]
}]
}]
console.log(findPath (treeData, 'id', 5))
Here you go!
function getPath(obj, value, path) {
if(typeof obj !== 'object') {
return;
}
for(var key in obj) {
if(obj.hasOwnProperty(key)) {
console.log(key);
var t = path;
var v = obj[key];
if(!path) {
path = key;
}
else {
path = path + '.' + key;
}
if(v === value) {
return path;
}
else if(typeof v !== 'object'){
path = t;
}
var res = getPath(v, value, path);
if(res) {
return res;
}
}
}
}
getPath(yourObject, valueYouWantToFindPath);
Rerutns path if found, else returns undefined.
I have only tested it with objects & comparison is very strict(ie: used ===).
Update:
Updated version that takes key as an argument.
function getPath(obj, key, value, path) {
if(typeof obj !== 'object') {
return;
}
for(var k in obj) {
if(obj.hasOwnProperty(k)) {
console.log(k);
var t = path;
var v = obj[k];
if(!path) {
path = k;
}
else {
path = path + '.' + k;
}
if(v === value) {
if(key === k) {
return path;
}
else {
path = t;
}
}
else if(typeof v !== 'object'){
path = t;
}
var res = getPath(v, key, value, path);
if(res) {
return res;
}
}
}
}
getPath(yourObject, key, valueYouWantToFindPath);
JSON Object can be handled in JavaScript as associative array.
So You can cycle through and store indexes of "parents" in some variables.
Assume the whole object to be stored in variable called obj.
for( var p1 in obj )
{
for( var p2 in obj[ p1 ] )
{
for( var p3 in obj[ p1 ][ p2 ] )
{
// obj[ p1 ][ p2 ][ p3 ] is current node
// so for Your example it is obj.obj1.obj2.data1
}
}
}
Hope answer was helpful.
I would do this job as follows;
Object.prototype.paths = function(root = [], result = {}) {
var ok = Object.keys(this);
return ok.reduce((res,key) => { var path = root.concat(key);
typeof this[key] === "object" &&
this[key] !== null ? this[key].paths(path,res)
: res[this[key]] == 0 || res[this[key]] ? res[this[key]].push(path)
: res[this[key]] = [path];
return res;
},result);
};
var myObj = {
obj1: {
obj2: {
data1: 213,
data2: "1231",
obj3: {
data: "milf"
}
}
},
obj4: {
description: "toto",
cougars: "Jodi",
category: "milf"
}
},
value = "milf",
milfPath = myObj.paths()[value]; // the value can be set dynamically and if exists it's path will be listed.
console.log(milfPath);
A few words of warning: We should be cautious when playing with the Object prototype. Our modification should have the descriptor enumerable = false or it will list in the for in loops and for instance jQuery will not work. (this is how silly jQuery is, since apparently they are not making a hasOwnProperty check in their for in loops) Some good reads are here and here So we have to add this Object method with Object.defineProperty() to make it enumerable = false;. But for the sake of simplicity and to stay in the scope of the question i haven't included that part in the code.
Here is a pretty short, and relatively easy to understand function I wrote for retrieving the JSON Path for every property/field on an Object (no matter how deeply nested, or not).
The getPaths(object) function just takes the Object you'd like the JSON Paths for and returns an array of paths. OR, if you would like the initial object to be denoted with a symbol that is different from the standard JSON Path symbol, $, you can call getPaths(object, path), and each JSON Path will begin with the specified path.
For Example: getPaths({prop: "string"}, 'obj'); would return the following JSON Path: obj.prop, rather than $.prop.
See below for a more detailed, in depth example of what getPaths returns, and how it is used.
object = {
"firstName": "John",
"lastName": "doe",
"age": 26,
"fakeData": true,
"address": {
"streetAddress": "fake street",
"city": "fake city",
"postalCode": "12345"
},
"phoneNumbers": [{
"type": "iPhone",
"number": "0123-4567-8888"
}, {
"type": "home",
"number": "0123-4567-8910"
}]
};
function getPaths(object, path = "$") {
return Object.entries(object).flatMap(function(o, i) {
if (typeof o[1] === "object" && !o[1].length) {
return `${getPaths(o[1], path + '.' + o[0])}`.split(',');
} else if (typeof o[1] === "object" && o[1].length) {
return Object.entries(o[1]).flatMap((no, i) => getPaths(no[1], `${path}.${o[0]}[${i}]`));
} else {
return `${path}.${o[0]}`;
}
});
}
console.log(`%o`, getPaths(object));
I really liked Roland Jegorov's answer, but I had a very complex object that I needed to search through and that answer could not account for it.
If you were in a situation like mine you may want to first make sure you have no circular references (or else you'll run into an infinite search). There are a few ways to do this, but I was having to stringify my object to copy it into other windows, so I ended up using this circular replacer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
(Update here - I made a small change to the getCircularReplacer function from MDN so it no longer leaves out function references since that is what I was looking for!)
(Update 3 - I also wanted to check on methods of any instances of classes, but I was returning just 'function' too early, so I have adjusted it to include instance methods. I think it finally works as I intended!)
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "function") {
if (value?.prototype) {
if (seen.has(value.prototype)) {
return;
}
seen.add(value.prototype)
return value.prototype
}
return "function";
}
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
const nonCyclicObject = JSON.parse(JSON.stringify(myComplexObject, getCircularReplacer()));
Then I used this modified version of Roland's answer:
(Update 2: I had to make sure not to return after the key was found as it would always simply return after only calling the function once if the first level of the object had that key)
function findPathsToKey(options) {
let count = 0;
let results = [];
(function findKey({
key,
obj,
pathToKey,
}) {
count += 1;
if (obj === null) return;
const oldPath = `${pathToKey ? pathToKey + "." : ""}`;
if (Object.hasOwnProperty.call(obj, key)) {
results.push(`${oldPath}${key}`);
}
if (typeof obj === "object" && !Array.isArray(obj)) {
for (const k in obj) {
if (Object.hasOwnProperty.call(obj, k)) {
if (Array.isArray(obj[k])) {
for (let j = 0; j < obj[k].length; j++) {
findKey({
obj: obj[k][j],
key,
pathToKey: `${oldPath}${k}[${j}]`,
});
}
}
if (typeof obj[k] === "object") {
findKey({
obj: obj[k],
key,
pathToKey: `${oldPath}${k}`,
});
}
}
}
}
})(options);
return { count, results };
};
The count was just to troubleshoot a little bit and make sure it was actually running through the amount of keys I thought it was. Hope this helps any others looking for a solution!
⚠️ This code doesn't answer the question but does related: transforms nested object to query object with dot.divided.path as keys and non-object values; compatible with URlSearchParams & qs. Maybe will be useful for someone.
const isPlainObject = (v) => {
if (Object.prototype.toString.call(v) !== '[object Object]') return false;
const prototype = Object.getPrototypeOf(v);
return prototype === null || prototype === Object.prototype;
};
const objectToQueryObject = (obj, path) => {
return Object.entries(obj).reduce((acc, [key, value]) => {
const newPath = path ? `${path}.${key}` : key;
if (isPlainObject(value)) {
return {
...acc,
...objectToQueryObject(value, newPath)
};
}
acc[newPath] = value;
return acc;
}, {})
};
const queryObjectRaw = {
value: {
field: {
array: {
'[*]': {
field2: {
eq: 'foo',
ne: 'bar',
}
}
},
someOtherProp: { in: [1, 2, 3],
ne: 'baz',
}
},
someOtherField: {
gt: 123
},
},
otherValue: {
eq: 2
},
};
const result = objectToQueryObject(queryObjectRaw);
console.log('result', result);
const queryString = new URLSearchParams(result).toString();
console.log('queryString', queryString);
If you know only the value and not the key, and want to find all paths with this value use this.
It will find all property with that value, and print the complete path for every founded value.
const createArrayOfKeys = (obj, value) => {
const result = []
function iter(o) {
Object.keys(o).forEach(function(k) {
if (o[k] !== null && typeof o[k] === 'object') {
iter(o[k])
return
}
if (o[k]=== value) {
result.push(k)
return
}
})
}
iter(obj)
return result
}
function findPath (obj, name, val, currentPath) {
currentPath = currentPath || ''
let matchingPath
if (!obj || typeof obj !== 'object') return
if (obj[name] === val) return `${currentPath}/${name}/${val}`
for (const key of Object.keys(obj)) {
if (key === name && obj[key] === val) {
matchingPath = currentPath
} else {
matchingPath = findPath(obj[key], name, val, `${currentPath}/${key}`)
}
if (matchingPath) break
}
return matchingPath
}
const searchMultiplePaths = (obj, value) => {
const keys = createArrayOfKeys(obj, value)
console.log(keys);
keys.forEach(key => {
console.log(findPath(obj, key, value))
})
}
var data = { ffs: false, customer: { customer_id: 1544248, z_cx_id: '123456' }, selected_items: { '3600196': [{ id: 4122652, name: 'Essential Large (up to 8\'x10\')', selected: true }] }, service_partner: { id: 3486, name: 'Some String', street: '1234 King St.', hop: '123456' }, subject: 'Project-2810191 - Orange Juice Stain (Rug)', description: 'Product Type: \n\nIssue: (copy/paste service request details here)\n\nAction Required:', yes: '123456' };
searchMultiplePaths(data, '123456')
I know the post is old but the answers don't really satisfy me.
A simple solution is to add the object path to each object in the structure. Then you can easily read the path when you need it.
let myObject = {
name: 'abc',
arrayWithObject: [
{
name: "def"
},
{
name: "ghi",
obj: {
name: "jkl"
}
}
],
array: [15, 'mno'],
arrayArrayObject: [
[
{
name: '...'
}
]
]
}
function addPath(obj, path = [], objectPathKey = '_path') {
if (Array.isArray(obj)) {
obj.map((item, idx) => addPath(item, [...path, idx]))
} else if (typeof obj === "object") {
obj[objectPathKey] = path;
for (const key in obj) {
obj[key] = addPath(obj[key], [...path, key])
}
}
return obj
}
myObject = addPath(myObject);
let changeMe = _.cloneDeep(myObject.arrayWithObject[0])
changeMe.newProp = "NEW"
changeMe.newNested = {name: "new", deeper: {name: "asdasda"}}
changeMe = addPath(changeMe, changeMe._path)
_.set(myObject, changeMe._path, changeMe);
When your updates are done sanitize your object and remove your _path property.
Advantages of this solution:
You do the work once
you keep your code simple
no need for own property checks
no cognitive overload
I can highly suggest you to use lodash for this problem.
In their documentation this should help you out
// using "_.where" callback shorthand
_.find(characters, { 'age': 1 });
// → { 'name': 'pebbles', 'age': 1, 'blocked': false }