199

I'm using ajax to submit a multipart form with array, text fields and files.

I append each VAR to the main data as so

var attachments = document.getElementById('files'); 
var data= new FormData();

for (i=0; i< attachments.files.length; i++){
    data.append('file', attachments.files[i]);
    console.log(attachments.files[i]);

    data.append ('headline', headline);
    data.append ('article', article);
    data.append ('arr', arr);
    data.append ('tag', tag);

then I use the ajax function to send it to a PHP file to store inside sql DB.

$.ajax({    
    type: "post",
    url: 'php/submittionform.php',
    cache: false,
    processData: false,
    contentType: false,
    data: data,
    success: function(request) {$('#box').html(request); }
})

But on the PHP side, the arr variable, which is an array appears as a string.

When I don't send it with ajax as Form data but use the simple $.POST option I do get it as an array on the PHP side, but then I can't send the files as well.

any solutions?

Samuel De Backer
  • 3,404
  • 2
  • 26
  • 37
shultz
  • 2,543
  • 3
  • 20
  • 23

15 Answers15

400

You can also send an array via FormData this way:

var formData = new FormData;
var arr = ['this', 'is', 'an', 'array'];

for (var i = 0; i < arr.length; i++) {
  formData.append('arr[]', arr[i]);
}

console.log(...formData);

So you can write arr[] the same way as you do it with a simple HTML form. In case of PHP it should work.

You may find this article useful: How to pass an array within a query string?

SuperDJ
  • 7,488
  • 11
  • 40
  • 74
Oleg
  • 22,300
  • 9
  • 68
  • 84
  • 2
    @Oleg What is the need to write `arr[]` in `formData.append('arr[]', arr[i]);`? why isn't `arr` correct? I tried both but only `arr[]` worked. – Totoro Apr 28 '18 at 14:59
  • @Totoro because in case of `arr` your just redefine this value on each loop iteration, and in the end, the final value would equal to last array element, but not the whole array – Oleg Apr 28 '18 at 15:03
  • @Oleg If redefining is the case then what's different in `arr[]` , why isn't `arr[]` redefined? `arr[]` is also a string . And while testing both neither `arr` nor `arr[]` was redefined in my case. I got multiple array in FormData with same key but different value. So i got `arr` with value `1` and another `arr` with value `2`. – Totoro Apr 28 '18 at 15:21
  • @Totoro yes, you're right, my mistake. I believe this is more serverside-specific question. Different languages may parse query string differently. For example, PHP behaves like you described, but I saw examples (if memory serves, in Java), where `arr` also worked for arrays. In [this topic](https://stackoverflow.com/questions/6243051/how-to-pass-an-array-within-a-query-string) there is a more detailed answer to this question – Oleg Apr 28 '18 at 15:30
  • 8
    If anyone is looking to post an array of objects, you can extend this answer as follows `for (var i = 0; i < myArr; i++) { var myItemInArr = myArr[i]; for (var prop in myItemInArr) { fileData.append(\`myArr[${i}][${prop}]\`, myItemInArr[prop]); } }` – edqwerty Sep 17 '18 at 12:39
  • @t I think this is part of the specification. – The-null-Pointer- Sep 20 '18 at 14:31
  • What about an array with only one item? Would that have to be handled specially? – Redmega Dec 11 '18 at 20:27
  • For posting an array of objects, you can also use: formData.append('arr[]', JSON.stringify(arr[i]); – jomofrodo Sep 18 '20 at 20:17
  • For me using react and axios i only did `arr` instead of `arr[]` and it worked – Aayush Neupane Jul 11 '23 at 14:51
152

You have several options:

Convert it to a JSON string, then parse it in PHP (recommended)

JS

var json_arr = JSON.stringify(arr);

PHP

$arr = json_decode($_POST['arr']);

Or use @Curios's method

Sending an array via FormData.


Not recommended: Serialize the data with, then deserialize in PHP

JS

// Use <#> or any other delimiter you want
var serial_arr = arr.join("<#>"); 

PHP

$arr = explode("<#>", $_POST['arr']);
Community
  • 1
  • 1
Richard de Wit
  • 7,102
  • 7
  • 44
  • 54
  • 1
    the problem is that the array contains lines of REAL text, with spaces and punctuation marks. I don't want to mess it up. – shultz Apr 19 '13 at 12:01
  • 4
    When you encode and parse it with JSON, data isn't lost. Give it a try ;) – Richard de Wit Apr 19 '13 at 12:03
  • If you are using asp.net with automatic mapping or something similar, then @Curious answer is what you need. – Martín Coll Jul 10 '15 at 19:50
  • 1
    @Richard de Wit If you have data such File or FormData you will lose them in json.stringfy – Mohsen Apr 24 '18 at 06:56
  • 1
    I like stringified better, simpler. As you need to do some sort of recursion to pass array of arrays using [] but good to know that can be done that way. – Chopnut May 20 '19 at 22:35
  • How to achieve the same when posting to a java server – Saurabh Tiwari Oct 10 '19 at 10:47
  • Hello. If you're still around, please can you explain the reasoning behind the first solution? It works great, but why does it work? Why does a JS array have to be converted into a JSON object? – The Codesee Feb 13 '21 at 15:24
  • 1
    Why is this the accepted answer when the keywords from the OP not only NOT list PHP, but start with JavaScript? – JurgenBlitz Jan 02 '23 at 11:49
16

Typescript version:

export class Utility {      
    public static convertModelToFormData(model: any, form: FormData = null, namespace = ''): FormData {
        let formData = form || new FormData();
        let formKey;

        for (let propertyName in model) {
            if (!model.hasOwnProperty(propertyName) || !model[propertyName]) continue;
            let formKey = namespace ? `${namespace}[${propertyName}]` : propertyName;
            if (model[propertyName] instanceof Date)
                formData.append(formKey, model[propertyName].toISOString());
            else if (model[propertyName] instanceof Array) {
                model[propertyName].forEach((element, index) => {
                    const tempFormKey = `${formKey}[${index}]`;
                    this.convertModelToFormData(element, formData, tempFormKey);
                });
            }
            else if (typeof model[propertyName] === 'object' && !(model[propertyName] instanceof File))
                this.convertModelToFormData(model[propertyName], formData, formKey);
            else
                formData.append(formKey, model[propertyName].toString());
        }
        return formData;
    }
}

Using:

let formData = Utility.convertModelToFormData(model);
Mohammad Dayyan
  • 21,578
  • 41
  • 164
  • 232
  • 1
    Thank you so much, it works fine! For those not using Typescript, you just need to remove the first and last line + replace the method declaration `public static` with `function` + remove the types declarations `: any`, `: FormData` and `: FormData`. – coccoinomane Oct 12 '21 at 14:42
  • As of now, the function skips zeros and empty strings. To include them, replace the `if` block at line 7 with: `if (!model.hasOwnProperty(propertyName) || (!model[propertyName] && model[propertyName]!==0 && model[propertyName]!=='')) continue;` – coccoinomane Oct 12 '21 at 14:53
8

This is an old question but I ran into this problem with posting objects along with files recently. I needed to be able to post an object, with child properties that were objects and arrays as well.

The function below will walk through an object and create the correct formData object.

// formData - instance of FormData object
// data - object to post
function getFormData(formData, data, previousKey) {
  if (data instanceof Object) {
    Object.keys(data).forEach(key => {
      const value = data[key];
      if (value instanceof Object && !Array.isArray(value)) {
        return this.getFormData(formData, value, key);
      }
      if (previousKey) {
        key = `${previousKey}[${key}]`;
      }
      if (Array.isArray(value)) {
        value.forEach(val => {
          formData.append(`${key}[]`, val);
        });
      } else {
        formData.append(key, value);
      }
    });
  }
}

This will convert the following json -

{
  name: 'starwars',
  year: 1977,
  characters: {
    good: ['luke', 'leia'],
    bad: ['vader'],
  },
}

into the following FormData

 name, starwars
 year, 1977
 characters[good][], luke
 characters[good][], leia
 characters[bad][], vader
VtoCorleone
  • 16,813
  • 5
  • 37
  • 51
  • It was usefull for me, just had to apply String(value) on value inside append (otherwise it fails for true/false). Also it should be `(value !== null) && formData.append(key, value)` instead of just `formData.append(key, value)` otherwise it fails on null values – Alexander Jun 13 '17 at 15:03
5

add all type inputs to FormData

const formData = new FormData();
for (let key in form) {
    Array.isArray(form[key])
        ? form[key].forEach(value => formData.append(key + '[]', value))
        : formData.append(key, form[key]) ;
}
HamidNE
  • 119
  • 3
  • 9
5

here's another version of the convertModelToFormData since I needed it to also be able to send Files.

utility.js

const Utility = {
  convertModelToFormData(val, formData = new FormData, namespace = '') {
    if ((typeof val !== 'undefined') && val !== null) {
      if (val instanceof Date) {
        formData.append(namespace, val.toISOString());
      } else if (val instanceof Array) {
        for (let i = 0; i < val.length; i++) {
          this.convertModelToFormData(val[i], formData, namespace + '[' + i + ']');
        }
      } else if (typeof val === 'object' && !(val instanceof File)) {
        for (let propertyName in val) {
          if (val.hasOwnProperty(propertyName)) {
            this.convertModelToFormData(val[propertyName], formData, namespace ? `${namespace}[${propertyName}]` : propertyName);
          }
        }
      } else if (val instanceof File) {
        formData.append(namespace, val);
      } else {
        formData.append(namespace, val.toString());
      }
    }
    return formData;
  }
}
export default Utility;

my-client-code.js

import Utility from './utility'
...
someFunction(form_object) {
  ...
  let formData = Utility.convertModelToFormData(form_object);
  ...
}
Zombiesplat
  • 943
  • 8
  • 19
3

Based on @YackY answer shorter recursion version:

function createFormData(formData, key, data) {
    if (data === Object(data) || Array.isArray(data)) {
        for (var i in data) {
            createFormData(formData, key + '[' + i + ']', data[i]);
        }
    } else {
        formData.append(key, data);
    }
}

Usage example:

var data = {a: '1', b: 2, c: {d: '3'}};
var formData = new FormData();
createFormData(formData, 'data', data);

Sent data:

data[a]=1&
data[b]=2&
data[c][d]=3
dikirill
  • 1,873
  • 1
  • 19
  • 21
3

TransForm three formats of Data to FormData :

1. Single value like string, Number or Boolean

 let sampleData = {
  activityName: "Hunting3",
  activityTypeID: 2,
  seasonAssociated: true, 
};

2. Array to be Array of Objects

let sampleData = {
   activitySeason: [
    { clientId: 2000, seasonId: 57 },
    { clientId: 2000, seasonId: 57 },
  ],
};

3. Object holding key value pair

let sampleData = {
    preview: { title: "Amazing World", description: "Here is description" },
};

The that make our life easy:

function transformInToFormObject(data) {
  let formData = new FormData();
  for (let key in data) {
    if (Array.isArray(data[key])) {
      data[key].forEach((obj, index) => {
        let keyList = Object.keys(obj);
        keyList.forEach((keyItem) => {
          let keyName = [key, "[", index, "]", ".", keyItem].join("");
          formData.append(keyName, obj[keyItem]);
        });
      });
    } else if (typeof data[key] === "object") { 
      for (let innerKey in data[key]) {
        formData.append(`${key}.${innerKey}`, data[key][innerKey]);
      }
    } else {
      formData.append(key, data[key]);
    }
  }
  return formData;
}

Example : Input Data

let sampleData = {
  activityName: "Hunting3",
  activityTypeID: 2,
  seasonAssociated: true,
  activitySeason: [
    { clientId: 2000, seasonId: 57 },
    { clientId: 2000, seasonId: 57 },
  ],
  preview: { title: "Amazing World", description: "Here is description" },
};

Output FormData :

activityName: Hunting3
activityTypeID: 2
seasonAssociated: true
activitySeason[0].clientId: 2000
activitySeason[0].seasonId: 57
activitySeason[1].clientId: 2000
activitySeason[1].seasonId: 57
preview.title: Amazing World
preview.description: Here is description
mabdullahse
  • 3,474
  • 25
  • 23
2

Next version valid for model containing arays of simple values:

function convertModelToFormData(val, formData = new FormData(), namespace = '') {
    if((typeof val !== 'undefined') && (val !== null)) {
        if(val instanceof Date) {
            formData.append(namespace, val.toISOString());
        } else if(val instanceof Array) {
            for(let element of val) {
                convertModelToFormData(element, formData, namespace + '[]');
            }
        } else if(typeof val === 'object' && !(val instanceof File)) {
            for (let propertyName in val) {
                if(val.hasOwnProperty(propertyName)) {
                    convertModelToFormData(val[propertyName], formData, namespace ? namespace + '[' + propertyName + ']' : propertyName);
                }
            }
        } else {
            formData.append(namespace, val.toString());
        }
    }
    return formData;
}
Megabyte
  • 66
  • 6
2

If you have nested objects and arrays, best way to populate FormData object is using recursion.

function createFormData(formData, data, key) {
    if ( ( typeof data === 'object' && data !== null ) || Array.isArray(data) ) {
        for ( let i in data ) {
            if ( ( typeof data[i] === 'object' && data[i] !== null ) || Array.isArray(data[i]) ) {
                createFormData(formData, data[i], key + '[' + i + ']');
            } else {
                formData.append(key + '[' + i + ']', data[i]);
            }
        }
    } else {
        formData.append(key, data);
    }
}
YackY
  • 53
  • 6
1

I've fixed the typescript version. For javascript, just remove type definitions.

  _getFormDataKey(key0: any, key1: any): string {
    return !key0 ? key1 : `${key0}[${key1}]`;
  }
  _convertModelToFormData(model: any, key: string, frmData?: FormData): FormData {
    let formData = frmData || new FormData();

    if (!model) return formData;

    if (model instanceof Date) {
      formData.append(key, model.toISOString());
    } else if (model instanceof Array) {
      model.forEach((element: any, i: number) => {
        this._convertModelToFormData(element, this._getFormDataKey(key, i), formData);
      });
    } else if (typeof model === 'object' && !(model instanceof File)) {
      for (let propertyName in model) {
        if (!model.hasOwnProperty(propertyName) || !model[propertyName]) continue;
        this._convertModelToFormData(model[propertyName], this._getFormDataKey(key, propertyName), formData);
      }
    } else {
      formData.append(key, model);
    }

    return formData;
  }
kaya
  • 724
  • 10
  • 24
1

If you are using django ArrayField so you need send data as example below

const form = new FormData();
const value = ["Hello", "World"];
const key = "arr";
let i;
for (i = 0; i < value.length; i++) {
    form.append(key, value[i]);
}
axios.post(url, form).then(res=> console.log(res));
Bambier
  • 586
  • 10
  • 16
0

JavaScript code:

var formData = new FormData();
let arr = [1,2,3,4];
formData.append('arr', arr);

Output on php:

$arr = $_POST['arr']; ===>  '1,2,3,4'

Solution php code:

$arr = explode(",", $_POST['arr']); ===> [1,2,3,4]
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
  • This could use some explanation that says why and how it works rather than just showing the code that you think works. – Newbyte Nov 24 '21 at 16:53
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 24 '21 at 16:53
0

In a case where you have an array of elements or maybe an object. You can simply use the set method together with JSON.stringify to convert the array to a string and set the value to that string. An tstringified json data back to a proper array JSON, set the form value, and on the backend convert back to a valid array with very little problems.

const bookItems = [
    {
      name: "John Doe",
      age: 21
    }
 ...
];
formData.set("books", JSON.stringify(bookItems));
-3

simple way :

data.map(dt=>formdata.append("name",dt))

i've tried it, it works perfectly

Blue
  • 15
  • 2
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Cristik Apr 21 '22 at 04:30
  • Your answer is of much lower quality than others – Zach Jensz Apr 25 '22 at 11:33