У меня есть функция сопоставления данных, которая принимает json с четырьмя уровнями данных, а затем сопоставляет их с другим форматом.
Введите формат JSON:
[{
"InventoryLevel2Id": "1234",
"InventoryLevel2Information": "Test Data",
"InventoryLevel2Name": "Test Data",
"InventoryLevel3s": [
{
"InventoryLevel3Id": "5678",
"InventoryLevel3Name": "Inner data at 1",
"InventoryLevel3Information": "Inner info at 1",
"InventoryLevel4s": [
{
"InventoryLevel4Id": "9101112",
"InventoryLevel4Name": "Inner data at 2",
"InventoryLevel4Information": "Inner info at 2",
"InventoryLevel5s": [
{
"InventoryLevel5Id": "131415",
"InventoryLevel5Name": "Inner data at 3",
"InventoryLevel5Information": "Inner info at 3",
}
],
}
]
}
]
}]
Выходной формат JSON:
[{
label1: 'TestData',
label2: "Test data",
uniquieId: "1234",
innerState: {
data : {
label1: 'Inner data at 1',
label2: "Inner info at 1",
uniquieId: "5678",
innerState: {
data: {
label1: 'Inner data at 2',
label2: "Inner info at 2",
uniquieId: "9101112",
innerState: {
data: {
label1: 'Inner data at 3',
label2: "Inner info at 4",
uniquieId: "131415",
}
}
}
}
}
}
}]
Для этого я написал эту функцию сопоставления. Это прекрасно работает, но мне приходится снова и снова вызывать картограф. Для оптимизации я думаю о создании рекурсивной функции, которая будет вызывать себя, пока не будет выполнено сопоставление для всех уровней.
const dataMapperFunc = (
inputData
) => {
const mappedData = inputData?.map((a) => ({
label1: a.InventoryLevel2Name,
label2: a.InventoryLevel2Information,
uniquieId: a.InventoryLevel2Id,
innerState: {
data: a.InventoryLevel3s?.map((b) => ({
label1: b.InventoryLevel3Name,
label2: b.InventoryLevel3Information,
uniquieId: b.InventoryLevel3Id,
innerState: {
data: b.InventoryLevel4s?.map(
(c) => ({
label1: c.InventoryLevel4Name,
label2: c.InventoryLevel4Information,
uniquieId: c.InventoryLevel4Id,
innerState: {
data: c.InventoryLevel5s?.map(
(d) => ({
label1: d.InventoryLevel5Name,
label2:
d.InventoryLevel5Information,
uniquieId: d.InventoryLevel5Id,
})
),
},
})
),
},
})),
},
}));
return mappedData;
};
Это моя попытка использовать функцию сопоставления, которая останавливается на InventoryLevel3s и не завершается. Я пропускаю несколько шагов, и это можно сделать лучше.
const dataMapperFuncTryReccursive = (inputData ) => {
const mappedData = inputData?.map((a) =>
mapper(
a,
"InventoryLevel2Name",
"InventoryLevel2Information",
"InventoryLevel3Id",
"InventoryLevel3s"
)
);
return mappedData;
};
const mapper = (entity, field1, field2, uniquieId, childEntityName) => {
if (entity) {
return {
label1: entity[field1],
label2: entity[field2],
uniquieId: entity[uniquieId],
concurrencyId: entity.concurrencyId,
innerState: {
data: entity[childEntityName]?.map((innerData) =>
mapper(
innerData,
"InventoryLevel3Name",
"InventoryLevel3Information",
"InventoryLevel3Id",
"InventoryLevel4s"
)
),
},
};
}
};
Может ли быть несколько значений на каждом уровне? Пример данных имеет только один.
@Ник, да, на каждом уровне может быть несколько значений. Каждый уровень представляет собой массив. И, согласитесь, данные внутри внутреннего состояния нам не понадобятся.
Вот рекурсивная функция, которая должна дать вам желаемые результаты. Он извлекает номер текущего уровня, заменяя все нечисловые значения из первого ключа входного объекта, а затем использует это, чтобы найти соответствующие свойства для копирования в выход:
const data = [{
"InventoryLevel2Id": "1234",
"InventoryLevel2Information": "Test Data",
"InventoryLevel2Name": "Test Data",
"InventoryLevel3s": [{
"InventoryLevel3Id": "5678",
"InventoryLevel3Name": "Inner data at 1",
"InventoryLevel3Information": "Inner info at 1",
"InventoryLevel4s": [{
"InventoryLevel4Id": "9101112",
"InventoryLevel4Name": "Inner data at 2",
"InventoryLevel4Information": "Inner info at 2",
"InventoryLevel5s": [{
"InventoryLevel5Id": "131415",
"InventoryLevel5Name": "Inner data at 3",
"InventoryLevel5Information": "Inner info at 3",
}],
}]
}]
}]
const mapdata = (data) => {
let level = +Object.keys(data)[0].replace(/[^\d]/g, '');
const obj = {
label1: data['InventoryLevel' + level + 'Name'],
label2: data['InventoryLevel' + level + 'Information'],
uniqueId: data['InventoryLevel' + level + 'Id']
};
level++;
if (data.hasOwnProperty('InventoryLevel' + level + 's')) {
obj.innerState = data['InventoryLevel' + level + 's'].map(mapdata);
}
return obj;
}
out = data.map(mapdata);
console.info(out);
Вот мой взгляд на это. Просто сопоставьте с подстановочным знаком число, и вы можете сделать это с помощью небольшой рекурсивной функции.
const data = [{
"InventoryLevel2Id": "1234",
"InventoryLevel2Information": "Test Data",
"InventoryLevel2Name": "Test Data",
"InventoryLevel3s": [
{
"InventoryLevel3Id": "5678",
"InventoryLevel3Name": "Inner data at 1",
"InventoryLevel3Information": "Inner info at 1",
"InventoryLevel4s": [
{
"InventoryLevel4Id": "9101112",
"InventoryLevel4Name": "Inner data at 2",
"InventoryLevel4Information": "Inner info at 2",
"InventoryLevel5s": [
{
"InventoryLevel5Id": "131415",
"InventoryLevel5Name": "Inner data at 3",
"InventoryLevel5Information": "Inner info at 3",
}
],
}
]
}
]
}]
const mapRecursive = (inputData) =>
inputData.map(item =>
Object.keys(item).reduce((obj, key) => {
if (key.match("InventoryLevel.Id")) return { ...obj, uniquieId: item[key] };
if (key.match("InventoryLevel.Name")) return { ...obj, label1: item[key] };
if (key.match("InventoryLevel.Information")) return { ...obj, label2: item[key] };
if (key.match("InventoryLevel.s")) return { ...obj, innerState: { data: mapRecursive(item[key]) } };
}, {})
);
console.info(mapRecursive(data));
Преимущество может заключаться в том, чтобы сделать это более декларативным за счет использования вспомогательной функции.
В этой версии используется список сопоставлений между регулярными выражениями и новыми ключами с логическим свойством, указывающим, где повторяться. Используя вспомогательную функцию transform
, код для этой задачи выглядит так:
const mapData = transform ([
{oldKey: /^InventoryLevel\d+Id$/, newKey: 'uniqueId'},
{oldKey: /^InventoryLevel\d+Name$/, newKey:'label1'},
{oldKey: /^InventoryLevel\d+Information$/, newKey: 'label2'},
{oldKey: /^InventoryLevel\d+s$/, newKey: 'innerState', recur: true},
])
Конечно, функция transform
теперь полезна для других подобных задач. Но здесь важна не только возможность повторного использования. Более важным является разбивка проблемы на преобразование узлов и рекурсию, с одной стороны, и детали конкретных преобразований узлов, с другой. Должно быть очень ясно, где добавлять дополнительные узлы.
Вот реализация transform
:
const transform = (config) => (objs) =>
objs .map ((obj) =>
Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => {
const {newKey = '', recur = false} = config .find (({oldKey}) => oldKey .test (k))
return newKey
? [[newKey, recur ? transform (config) (v) : v]]
: []
}))
)
const mapData = transform ([
{oldKey: /^InventoryLevel\d+Id$/, newKey: 'uniqueId'},
{oldKey: /^InventoryLevel\d+Name$/, newKey:'label1'},
{oldKey: /^InventoryLevel\d+Information$/, newKey: 'label2'},
{oldKey: /^InventoryLevel\d+s$/, newKey: 'innerState', recur: true},
])
const input = [{InventoryLevel2Id: "1234", InventoryLevel2Information: "Test Data", InventoryLevel2Name: "Test Data", InventoryLevel3s: [{InventoryLevel3Id: "5678", InventoryLevel3Name: "Inner data at 1", InventoryLevel3Information: "Inner info at 1", InventoryLevel4s: [{InventoryLevel4Id: "9101112", InventoryLevel4Name: "Inner data at 2", InventoryLevel4Information: "Inner info at 2", InventoryLevel5s: [{InventoryLevel5Id: "131415", InventoryLevel5Name: "Inner data at 3", InventoryLevel5Information: "Inner info at 3"}]}]}]}]
console .log (mapData (input))
.as-console-wrapper {max-height: 100% !important; top: 0}
Обратите внимание, что вывод соответствует формату ответа Ника. Запрошенный формат не соответствует комментарию «Да, на каждом уровне может быть несколько значений. Каждый уровень — это массив», даже после удаления узла data
. Дело в том, что элементы innerState
— это массивы, а не простые объекты.
Есть много места, чтобы сделать transform
более общим. Но есть компромисс. Чем более общим мы это сделаем, тем больше нам придется сделать в конфигурации. Для сравнения, моя первая версия выглядела примерно так:
const transform = (config) => (objs) =>
objs .map ((obj) =>
Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) =>
(config .find (({test}) => test(k)) || {result: () => []}) .result (v)
))
)
const mapData = transform ([
{test: (k) => /^InventoryLevel\d+Id$/.test(k), result: (v) => [['uniqueId', v]]},
{test: (k) => /^InventoryLevel\d+Name$/.test(k), result: (v) => [['label1', v]]},
{test: (k) => /^InventoryLevel\d+Information$/.test(k), result: (v) => [['label2', v]]},
{test: (k) => /^InventoryLevel\d+s$/.test(k), result: (v) => [['innerState', mapData (v)]]},
])
Обратите внимание, что transform
просто сильнее. Он допускает произвольные тестовые функции в конфигурации в сочетании с функциями вывода, которые могут создавать любые необходимые узлы. Например, он может превратить один входной узел в три выходных. Но обратите внимание, насколько сложнее массив конфигурации. Вместо {oldKey: /^InventoryLevel\d+Id$/
нам нужно test: (k) => /^InventoryLevel\d+Id$/.test(k)
. А вместо result: (v) => [['uniqueId', v]]
у нас result: (v) => [['uniqueId', v]]
. Более того, мы должны явно вызывать рекурсию, вместо того, чтобы отмечать ее логическим свойством.
Мы могли бы пойти и в другом направлении, если бы захотели, сделав конфигурацию еще проще за счет немного более сложной функции transform
. Возможно, так было бы лучше:
const transform = (config) => (objs) =>
objs .map ((obj) =>
Object .fromEntries (Object .entries (obj) .flatMap (([k, v]) => {
const {newKey, recur} = config .find (
({oldKey}) => new RegExp (`^${oldKey .replace ('#', '\\d+')}$`) .test (k)
)
return newKey
? [[newKey, recur ? transform (config) (v) : v]]
: []
}))
)
const mapData = transform ([
{oldKey: 'InventoryLevel#Id', newKey: 'uniqueId'},
{oldKey: 'InventoryLevel#Name', newKey:'label1'},
{oldKey: 'InventoryLevel#Information', newKey: 'label2'},
{oldKey: 'InventoryLevel#s', newKey: 'innerState', recur: true},
])
Теперь нашей конфигурации ничего не нужно знать о регулярных выражениях, и мы можем просто использовать подстановочный знак #
для набора цифр. Это делает transform
еще менее универсальным и менее мощным, но упрощает настройку. Если вы уверены, что никогда не будете использовать transform
повторно, это, вероятно, хороший выбор.
Почему у вас есть дополнительный уровень вложенности с элементом
data
внутриinnerState
? кажется ненужным, поскольку он единственный.