48

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'}?

2
  • 2
    Do you really need this structure as is on server side? in this case you'd have to traverse your object and use a key[subkey] notation, e.g formData.append('preview[p_title]', 'p title');. But otherwise, simply send it as a JSON string and parse it server side, usually the simplest both to send and retrieve. Commented Sep 29, 2018 at 11:05
  • Actually I am new in backend coding , my mongodb structure is nested that why I choose this structure from clientside to send data, I thought this was is the right way, I think simplest way that dont try pass nested structure from client side and just re-structure from server before I send to db @Kaiido Commented Oct 1, 2018 at 6:18

12 Answers 12

24
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]); 
}
Sign up to request clarification or add additional context in comments.

3 Comments

Having a file as value JSON.stringify didn't work for me, this did.
This is an excellent answer! And, also safer than parsing JSON from the client!
How can i implement it for dynamic field like that preview: [ {p_title:'p title1', p_text: 'p text1'}, {p_title:'p title2', p_text: 'p text2'} ] Thanks if you help me!
15

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!

5 Comments

It is FormData, You've written FormDate
@ustaad Hey thanks for the heads up. I have updated it.
only works when the server expects json
What if you have file within formdata.
If you have file in formdata then don't stringify the whole formdata instead do it like this: formData.append("centerAccess", JSON.stringify(values.centerAccess)); formData.append("pageAccess", JSON.stringify(values.pageAccess)); formData.append("password", values.password); if (signature) formData.append("signature", signature); here signature is a file.
11

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.

3 Comments

This is a good proposition, however it has some tweaks. I'll add a version that contains some improvements of your version below (I've used your approach as a template, and then noticed some errors)!
I will follow your update with interest. Thankyou.
@EmrahTuncel I like the answer but unfortunately didn't work. Here is what I get in the server: 'style_sizes[0][size]': ['1']. Here is my question with detail if you can have a look: stackoverflow.com/questions/74192626/…
8

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);
});

2 Comments

Added snippet @ShibinRagh
Why would you convert it to a Blob? Simply send the JSON as string, you won't win anything by sending it as multipart binary since it is not binary data.
7

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);

Comments

7

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]);
  }
}

Comments

4

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);

1 Comment

This looks pretty sweet at first glance
2

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]);
    }

2 Comments

Will this work if this is an array of objects? formData.append(prop + [${index}], item || '') This will append the object at the index which won't work?
I made the code a bit more precise. I have also shared the data used for testing. It worked well and filtered out unwanted elements like functions or classes. I hope it will be useful for you.
1

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

7 Comments

yeah, nice one. How does this have no votes?
@ThaJay Probably because it won't work for actually nested objects, which the question title asks about. This will only work for simple flat objects.
@Svish you're completely right. It should be a recursive function, the callback should call its parent to go a level deeper if the value is object or array.
@ThaJay That won't be enough, because the FormData keys would be a mess, unless you do something clever with those.
@ThaJay No idea what "jQuery's default request data encoding" looks like, but there are definitely ways to recursively encode FormData key-value pairs in a way that's possible to decode back into objects and arrays on the backend-side. For example the way object-to-formdata encodes the keys should pretty much work out of the box if posted to a PHP-script.
|
1

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]);
  }
}

1 Comment

it works fine. Thanks
0

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)

Comments

0

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.

3 Comments

Means if it is an array then just append it as it is to the formData? Will it work if there was an object having value of array of objects. 'abc': [{'a': 'a'}, {'b': 'b'}]
Here is what I got using this: 'style_sizes': ['[object Object]']. Here is my question if you have time. stackoverflow.com/questions/74192626/…
No I did not account for that usecase. Your example of an array holding objects enters the second else condition, which stringifies your array element objects, as done in the original issue. So you may split up the typeof currentValue === 'object' && !Array.isArray(currentValue) into a parent condition typeof currentValue === 'object'and then handle !Array.isArray(currentValue) and Array.isArray(currentValue) within that. If it's an array, iterate through the elements and trigger the recursion if the iterated array element is an object, otherwise add the value. Should do the trick.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.