In Laravel, I’m performing a JSON map where I need to transform a JSON to a different schema. To do that I’m following these steps:
- Dot the array with Arr::dot helper. This way I have this result, example:
array(11) {
["0.identificador"]=>
int(1)
["0.nome_completo"]=>
string(8) "John Doe"
["0.email"]=>
string(11) "[email protected]"
["0.empresa.razao_social"]=>
string(11) "ABC Company"
["0.empresa.endereco"]=>
string(11) "123 Main St"
["0.pedidos.0.SKU"]=>
int(1)
["0.pedidos.0.descricao"]=>
string(5) "Shoes"
["0.pedidos.0.qtd"]=>
int(2)
["0.pedidos.1.SKU"]=>
int(2)
["0.pedidos.1.descricao"]=>
string(5) "Shirt"
["0.pedidos.1.qtd"]=>
int(1)
}
- Iterate through the dotted array and compare with my keys map. If they match, I set the value with the mapped key.
My mapping looks like this:
array(8) {
["*.identificador"]=> string(4) "*.id"
["*.nome_completo"]=> string(6) "*.name"
["*.empresa"]=> string(9) "*.company"
["*.empresa.razao_social"]=> string(14) "*.company.name"
["*.empresa.endereco"]=> string(17) "*.company.address"
["*.pedidos.*.SKU"]=> string(13) "*.orders.*.id"
["*.pedidos.*.descricao"]=> string(18) "*.orders.*.product"
["*.pedidos.*.qtd"]=> string(19) "*.orders.*.quantity"
}
And this is what my code looks like so far:
$payload = [
[
"identificador" => 1,
"nome_completo" => "John Doe",
"email" => "[email protected]",
"empresa" => [
"razao_social" => "ABC Company",
"endereco" => "123 Main St"
],
"pedidos" => [
[
"SKU" => 1,
"descricao" => "Shoes",
"qtd" => 2
],
[
"SKU" => 2,
"descricao" => "Shirt",
"qtd" => 1
]
]
]
];
$dottedPayload = Arr::dot($payload);
$transformedPayload = [];
foreach ($dottedPayload as $key => $value) {
$newKey = preg_replace('/d+./', '*.', $key);
$newKey = preg_replace('/.d+./', '.*.', $newKey);
$newKey = preg_replace('/.d+$/', '.*', $newKey);
$mappedKey = $mappingConfig[$newKey] ?? $key;
data_fill($transformedPayload, $mappedKey, $value);
}
The output I have is this:
array(2) {
["*"]=>
array(4) {
["id"]=>
int(1)
["name"]=>
string(8) "John Doe"
["company"]=>
array(2) {
["name"]=>
string(11) "ABC Company"
["address"]=>
string(11) "123 Main St"
}
["orders"]=>
array(1) {
["*"]=>
array(3) {
["id"]=>
int(2)
["product"]=>
string(5) "Shirt"
["quantity"]=>
int(1)
}
}
}
[0]=>
array(1) {
["email"]=>
string(11) "[email protected]"
}
}
But this is the expected:
$expectedResult = [
[
"id" => 1,
"name" => "John Doe",
"email" => "[email protected]",
"company" => [
"name" => "ABC Company",
"address" => "123 Main St"
],
"orders" => [
[
"id" => 1,
"product" => "Shoes",
"quantity" => 2
],
[
"id" => 2,
"product" => "Shirt",
"quantity" => 1
]
]
]
];
How can I properly undot the array after the mapping, with the wildcards, setting the indexes correctly? Any other suggestions to perform this JSON mapping are also welcomed.
2
Answers
I like to implement data mapping using the Spatie Laravel Data package.
The Spatie package uses classes to define data objects which are filled from arrays or json data. The names of the properties are the field names you want to use in your code and the package provides a MapInputName attribute modifier to automatically translate incoming data names.
First we define the data classes and the field mapping:
When you receive the payload data pass it to the static constructor
from(...)
or use dependency injection to get data from a Request:The
toArray
function gives the output:Not only does the package provide these mappings but it can also validate the data through attribute modifiers and be passed to
Model::create
to make Eloquent objects easily.As you want the mapping of key names to be user configurable you can modify your original approach to use capture groups, these keep the numeric counters inserted by the
Arr::dot
function.Which gives the result:
The inefficiency in this approach is the nested loop; but in order to understand how it works, and if you only have a few mappings, it’s not a big concern.