anexar array a FormData e enviar via AJAX

108

Estou usando ajax para enviar um formulário multiparte com array, campos de texto e arquivos.

Anexo cada VAR aos dados principais, assim

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

então eu uso a função ajax para enviá-lo a um arquivo PHP para armazenar dentro do banco de dados sql.

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

Mas no lado do PHP, a arrvariável, que é um array, aparece como uma string.

Quando eu não o envio com ajax como dados de formulário, mas uso a $.POSTopção simples, eu o obtenho como um array no lado do PHP, mas não consigo enviar os arquivos também.

alguma solução?

shultz
fonte

Respostas:

92

Você tem várias opções:

Converta-o em uma string JSON e analise-o em PHP (recomendado)

JS

var json_arr = JSON.stringify(arr);

PHP

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

Ou use o método de @Curios

Enviando uma matriz por FormData.


Não recomendado: serialize os dados com e depois desserialize em PHP

JS

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

PHP

$arr = explode("<#>", $_POST['arr']);
Richard de Wit
fonte
1
o problema é que o array contém linhas de texto REAL, com espaços e sinais de pontuação. Eu não quero bagunçar tudo.
shultz de
3
Quando você o codifica e analisa com JSON, os dados não são perdidos. Experimente;)
Richard de Wit
Se você estiver usando asp.net com mapeamento automático ou algo semelhante, a resposta @Curious é o que você precisa.
Martín Coll
1
@Richard de Wit Se você tiver dados como Arquivo ou FormData, você os perderá em json.stringfy
Mohsen
Gosto mais de stringified, mais simples. Como você precisa fazer algum tipo de recursão para passar array de arrays usando [], mas é bom saber que pode ser feito dessa forma.
Chopnut de
260

Você também pode enviar uma matriz FormDatadesta forma:

var formData = new FormData;
var arr = ['this', 'is', 'an', 'array'];
for (var i = 0; i < arr.length; i++) {
    formData.append('arr[]', arr[i]);
}

Portanto, você pode escrever arr[]da mesma maneira que faria com um formulário HTML simples. No caso do PHP, deve funcionar.

Você pode achar este artigo útil: Como passar uma matriz em uma string de consulta?

Oleg
fonte
1
@Oleg Qual é a necessidade de escrever arr[]em formData.append('arr[]', arr[i]);? porque não está arrcorreto? Tentei os dois, mas só arr[]funcionou.
Totoro
@Totoro porque no caso de arrvocê apenas redefinir este valor em cada iteração do loop, e no final, o valor final seria igual ao último elemento da matriz, mas não a matriz inteira
Oleg
@Oleg Se redefinir é o caso, então o que é diferente em arr[], por que não é arr[]redefinido? arr[]também é uma string. E ao testar ambos nem arrnem arr[]foi redefinido no meu caso. Eu tenho vários array em FormData com a mesma chave, mas com valor diferente. Então eu peguei arrcom valor 1e outra arrcom valor 2.
Totoro
@Totoro sim, você está certo, erro meu. Eu acredito que esta é uma questão mais específica do servidor. Idiomas diferentes podem analisar string de consulta de maneira diferente. Por exemplo, o PHP se comporta como você descreveu, mas eu vi exemplos (se a memória servir, em Java), onde arrtambém funcionou para arrays. Em este tema há uma resposta mais detalhada a esta pergunta
Oleg
Se alguém quiser postar uma série de objetos, você pode estender esta resposta da seguinte maneirafor (var i = 0; i < myArr; i++) { var myItemInArr = myArr[i]; for (var prop in myItemInArr) { fileData.append(`myArr[${i}][${prop}]`, myItemInArr[prop]); } }
edqwerty
7

Esta é uma questão antiga, mas recentemente encontrei este problema ao postar objetos junto com arquivos. Eu precisava ser capaz de postar um objeto, com propriedades filho que eram objetos e matrizes também.

A função abaixo irá percorrer um objeto e criar o objeto formData correto.

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

Isso converterá o seguinte json -

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

no seguinte FormData

 name, starwars
 year, 1977
 characters[good][], luke
 characters[good][], leia
 characters[bad][], vader
VtoCorleone
fonte
Foi útil para mim, apenas tive que aplicar String (valor) no valor dentro de append (caso contrário, falha para verdadeiro / falso). Além disso, deveria ser em (value !== null) && formData.append(key, value)vez de apenas formData.append(key, value)falhar em valores nulos
Alexander
7

Versão datilografada:

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

Usando:

let formData = Utility.convertModelToFormData(model);
Mohammad Dayyan
fonte
ótimo trabalho, super útil: D
Cosimo Chellini
2

adicione todas as entradas de tipo a 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
fonte
2

Se você tiver objetos e matrizes aninhados, a melhor maneira de preencher o objeto FormData é usando recursão.

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
fonte
1

Próxima versão válida para modelo contendo matrizes de valores simples:

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
fonte
1

Com base na versão de recursão mais curta da resposta @YackY:

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

Exemplo de uso:

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

Dados enviados:

data[a]=1&
data[b]=2&
data[c][d]=3
dikirill
fonte