Undot Array with Wildcards in Laravel

107 Views Asked by At

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:

  1. 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)
}
  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.

1

There are 1 best solutions below

2
Tony On

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:

use Spatie\LaravelData\Attributes\DataCollectionOf;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\DataCollection;

class Company extends Data
{
  public function __construct(
    #[MapInputName('razao_social')] 
    public string $name,
    #[MapInputName('endereco')] 
    public string $address
  ) { }
}

class Order extends Data
{
  public function __construct(
    #[MapInputName('SKU')] 
    public int $id,
    #[MapInputName('descricao')] 
    public string $product,
    #[MapInputName('qtd')] 
    public int $quantity
  ) { }
}

class Payload extends Data
{
  public function __construct(
    #[MapInputName('identificador')] 
    public int $id,
    #[MapInputName('nome_completo')] 
    public string $name,
    #[MapInputName('empresa')]
    public Company $company,
    #[MapInputName('pedidos')]
    #[DataCollectionOf(Order::class)]
    public DataCollection $orders
  ) { }
}

When you receive the payload data pass it to the static constructor from(...) or use dependency injection to get data from a Request:

$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
            ]
        ]
    ]
];

Payload::from($payload[0])->toArray();

The toArray function gives the output:

[
  "id" => 1,
  "name" => "John Doe",
  "company" => [
    "name" => "ABC Company",
    "address" => "123 Main St",
  ],
  "orders" => [
    [
      "id" => 1,
      "product" => "Shoes",
      "quantity" => 2,
    ],
    [
      "id" => 2,
      "product" => "Shirt",
      "quantity" => 1,
    ],
  ],
]

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.