P-James's avatar
Level 12

FormData append javascript array (JSON.stringify), how to cast as PHP array?

I'm using Vue/ inertia, to submit a javascript array to be stored as JSON in my DB. Because I'm submitting files in the same form request, I'm using the javascript FormData object as advised in the docs here

For FormData.append() to work with an array, you need to JSON.stringify it.

let barArray = [{item: 'one'}, {item: 'two'}];

let data = FormData();

data.append('foo', JSON.stringify(barArray))

this.$inertia.post('/upload', data);

The problem is the request is being handled as a string, and data retreived from DB is still a string.

I have tried both the following but it's still a string.

// App/Models/Model.php

protected $casts = ['foo' => 'array'];

// also tried:

 public function setFooAttribute($value)
    {
        $this->attributes['foo'] = json_decode($value);
    }

If I DON'T use FormData(), then I get my PHP array as intended. So I think the best solution is to get the cast working somehow? Or I think a workaround would be to do something like

let data = new FormData();

this.$inertia.post("/keywords", {
        data,
        ...bar,
      });

Any suggestions are appreciated!

0 likes
4 replies
rodrigo.pedra's avatar
Level 56

When you stringify the array on the frontend, Laravel receives it as a string (as it is one) and saves it as an string.

You won't even be able to use array-based validation on that string before converting it back.

One simple option is:

Keep the casts on your model

protected $casts = ['foo' => 'json'];

Decode the JSON string before assigning it to the model for the first time

// on your controller that handle the ajax request
$model->foo = json_decode($request->input('foo'));
1 like
P-James's avatar
Level 12

@rodrigo.pedra Thanks for the information. Useful to know about the array-based validation not being an option.

This actually all works as expected, but I was confused because I expected to see an array when I dd(foo)

It looks like both these actually work

protected $casts ['foo' => 'json'];
// Or
protected $casts ['foo' => 'array'];
rodrigo.pedra's avatar

Both works, actually they execute the same code:

https://github.com/laravel/framework/blob/17e467cd132c6ccf60a02e00d3bdd4541a775c9f/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php#L543-L545

// Illuminate\Database\Eloquent\Concerns\HasAttributes

case 'array':
case 'json':
    return $this->fromJson($value);

I prefer to use json over array when it is an array of objects, so the code communicates better its intent.

Regarding your expectation to see the input automatically converted to an array, that actually would allow a user to type a JSON string on a field where you expect a plain string (for example name or email) and make your code fail as you were not expecting an object.

If you want to use array-based validation on that input you could send it as an array from the frontend, it is more work to do, as I said that was the simple solution.

This link has a snippet on how to do this:

https://stackoverflow.com/a/28434829

For reference I will copy the code from the link above here:

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

That way, when parsing the form input, PHP would populate the arr variable above as an array.

In your case, you could try something like this:

let barArray = [{item: 'one'}, {item: 'two'}];
let data = FormData();

barArray.forEach(function (element) {
    data.append('foo[][item]', element.item);
})

this.$inertia.post('/upload', data);

This way PHP would parse foo as an array of associative-arrays, and you wouldn't need to use json_decode when assigning foo to a model.

A library I use in my JavaScript to make this use cases easier to handle is this one from Spatie:

https://github.com/spatie/form-backend-validation

It handles nested objects, array of objects and file uploads for you.

2 likes

Please or to participate in this conversation.