В Laravel я создаю карту JSON, где мне нужно преобразовать JSON в другую схему. Для этого я выполняю следующие шаги:
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)
}
Моя карта выглядит так:
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"
}
И вот как выглядит мой код:
$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);
}
У меня есть такой результат:
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]"
}
}
Но это ожидаемо:
$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
]
]
]
];
Как я могу правильно отменить массив после сопоставления с помощью подстановочных знаков и правильно установить индексы? Также приветствуются любые другие предложения по выполнению этого сопоставления JSON.
Да! Я уже испробовал все помощники, но не смог добиться ожидаемого результата. Интересно, возможно ли это без написания собственной рекурсивной функции для обработки подстановочных знаков?
Мне нравится реализовывать сопоставление данных с помощью пакета Spatie Laravel Data.
Пакет Spatie использует классы для определения объектов данных, которые заполняются из массивов или данных JSON. Имена свойств — это имена полей, которые вы хотите использовать в своем коде, а пакет предоставляет модификатор атрибута MapInputName для автоматического перевода имен входящих данных.
Сначала мы определяем классы данных и сопоставление полей:
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
) { }
}
Когда вы получаете полезные данные, передайте их статическому конструктору from(...)
или используйте внедрение зависимостей, чтобы получить данные из запроса:
$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();
Функция toArray
дает результат:
[
"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,
],
],
]
Пакет не только предоставляет эти сопоставления, но также может проверять данные с помощью модификаторов атрибутов и передавать их в Model::create
, чтобы легко создавать объекты Eloquent.
Это очень круто. Вам когда-нибудь приходилось обрабатывать данные, которые может настроить пользователь? Например: клиент API, в котором пользователь может настроить API для получения данных и отправки данных, преобразованных в другой API, все поля, которые будут сопоставлены, настраиваются пользователем. Можно ли как-то настроить сопоставления в этом сценарии?
Вы не упомянули, что отображение должно настраиваться пользователем;) Пакет Spatie не совсем подходит для этой ситуации, поскольку вам нужно определить классы данных. У меня есть альтернативное решение, которое я опубликую как отдельный ответ.
Поскольку вы хотите, чтобы сопоставление имен клавиш настраивалось пользователем, вы можете изменить свой первоначальный подход, чтобы использовать группы захвата, при этом числовые счетчики будут сохраняться с помощью функции Arr::dot
.
$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
]
]
]
];
$regexMapping = [
'identificador' => 'id',
'nome_completo' => 'name',
'email' => 'email',
'empresa\.razao_social' => 'company.name',
'empresa\.endereco' => 'company.address',
'pedidos\.(\d+)\.SKU' => 'orders.$1.id',
'pedidos\.(\d+)\.descricao' => 'orders.$1.product',
'pedidos\.(\d+)\.qtd' => 'orders.$1.quantity',
];
$dotted = Arr::dot($payload);
$newPayload = [];
$replacements = 0;
foreach ($dotted as $key => $value) {
foreach ($regexMapping as $pattern => $replacement) {
$newKey = preg_replace('/'.$pattern.'/', $replacement, $key, -1, $replacements);
if ($replacements) {
// only store the new key if we replaced something
$newPayload[$newKey] = $value;
// match found, no need to check any more patterns
break;
}
}
}
Arr::undot($newPayload);
Что дает результат:
[
[
"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,
],
],
],
]
Неэффективностью этого подхода является вложенный цикл; но чтобы понять, как это работает, и если у вас всего несколько сопоставлений, это не является большой проблемой.
Спасибо за ваше предложение. Исходя из этого, для решения моей проблемы использовать это сопоставление регулярных выражений было намного проще, чем использовать рекурсию для обработки подстановочных знаков.
«Отменить Dot Array в Laravel» Вы пробовали метод Arr::undot?