Напишите рекурсивную функцию для сопоставления данных для всех уровней

У меня есть функция сопоставления данных, которая принимает 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"
          )
        ),
      },
    };
  }
};

Почему у вас есть дополнительный уровень вложенности с элементом data внутри innerState? кажется ненужным, поскольку он единственный.

Nick 15.12.2020 03:10

Может ли быть несколько значений на каждом уровне? Пример данных имеет только один.

Nick 15.12.2020 03:14

@Ник, да, на каждом уровне может быть несколько значений. Каждый уровень представляет собой массив. И, согласитесь, данные внутри внутреннего состояния нам не понадобятся.

Simsons 15.12.2020 03:25
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
3
198
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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

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 повторно, это, вероятно, хороший выбор.

Другие вопросы по теме