У меня есть функция сопоставления данных, которая принимает 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"
)
),
},
};
}
};
Может ли быть несколько значений на каждом уровне? Пример данных имеет только один.
@Ник, да, на каждом уровне может быть несколько значений. Каждый уровень представляет собой массив. И, согласитесь, данные внутри внутреннего состояния нам не понадобятся.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вот рекурсивная функция, которая должна дать вам желаемые результаты. Он извлекает номер текущего уровня, заменяя все нечисловые значения из первого ключа входного объекта, а затем использует это, чтобы найти соответствующие свойства для копирования в выход:
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? кажется ненужным, поскольку он единственный.