Исправлено: JS рекурсивная функция для получения вложенных (многоуровневых) дочерних объектов в виде массива объектов. - PullRequest
0 голосов
/ 04 октября 2019

Я хочу получить вывод в виде:

options: [{
  id: 'parent1',
  label: 'parent1',
  children: [{
    id: 'child1',
    label: 'child1',
    children: [{
      id: 'lastChild1',
      label: 'lastChild1',
    }]
  }, { 
    id: 'child2',
    label: 'child2',
    children: [{
      id: 'lastChild2',
      label: 'lastChild2',
    }]
  }]
}]

Однако вывод из getOptions() имеет формат, в котором массив дочерних свойств объекта parent1 содержит только второй дочерний элемент в приведенном вышеформат, первый ребенок отчасти перезаписан или не посещен циклом for..in в recurseList().

Может ли кто-нибудь исправить код для вывода первого дочернего элемента child1 вместе с child2,в основном, любой уровень вложенности.

var myObj = {
  parent1: {
    child1: {
      lastChild1: { test: 'cool'}
    },
    child2: {
      lastChild2: { test: 'cool'}
    }
  },
  parent2: {
    child2_1: {
      lastChild2_1: { test: 'cool'}
    },
    child2_2: {
      lastChild2_2: { test: 'cool'}
    }
  }
}

var result = getOptions(myObj)
console.log('result', result)

function getOptions(obj) {
  var options = []
  for (key in obj) {
    var data = recurseList(obj[key])
    options.push(data)
  }
  return options
}

function recurseList(obj) {
  let data= {}
  let option= []
  for (key in obj) {
    data.id = key
    data.label = key
    data.children = []

    if(obj[key] instanceof Object) {
      var val = recurseList(obj[key])
      data.children.push(val)
    }
  }
  return data
}

На самом деле, я хочу получить данные из моей базы данных реального времени Firebase, как показано на рисунке ниже:

firebase real-time-database snapshot

в формате для этого плагина vuejs: https://vue -treeselect.js.org

Спасибо

Ответы [ 3 ]

2 голосов
/ 04 октября 2019

переменная глубина

Вот адаптация к удивительному ответу Скотта, который позволяет вам преобразовать вложенную структуру в контролируемую пользователем глубину;convertUntil -

  1. Если вход o не является объектом (базовый случай), преобразовать нечего, верните вход
  2. В противном случае (индуктивно)вход является объектом. Если объект проходит пользовательский test, прекращает вложение и возвращает children: {}
  3. В противном случае (индуктивный) вход является объектом, но не не передать пользовательский test. Нанесите на карту входной объект и создайте уровень нашей выходной структуры. Повторите convertUntil для каждого значения объекта.

Нумерованные комментарии выше соответствуют приведенному ниже коду -

const identity = x =>
  x

const convertUntil = (test = identity, o = {}) =>
  Object (o) !== o  // 1
    ? o
: test (o)          // 2
    ? {}
: Object
    .entries (o)    // 3
    .map
      ( ([ k, v ]) =>
          ({ id: k, label: k, children: convertUntil (test, v) })
      )

const myObj =
  { parent1:
      { child1: { lastChild1: { test: 'cool' } }
      , child2: { lastChild2: { test: 'cool' } }
      }
  , parent2:
      { child2_1: { lastChild2_1: { test: 'cool' } }
      , child2_2: { lastChild2_2: { test: 'cool' } }
      }
  }

console .log (convertUntil (x => x.test === "cool", myObj))

Хотя я предпочитаю эту более согласованную структуру данных, я могу понять, если вам не нравится пустой children: {}, созданный выше. С небольшой модификацией мы можем удалить пустые children свойства -

const identity = x =>
  x

const convertUntil = (test = identity, o = {}) =>
  Object (o) !== o
    ? o
: Object
    .entries (o)
    .map
      ( ([ k, v ]) =>
          test (v) // <-- test here
            ? { id: k, label: k } // <-- no children
            : { id: k, label: k, children: convertUntil (test, v) }
      )

const myObj =
  { parent1:
      { child1: { lastChild1: { test: 'cool' } }
      , child2: { lastChild2: { test: 'cool' } }
      }
  , parent2:
      { child2_1: { lastChild2_1: { test: 'cool' } }
      , child2_2: { lastChild2_2: { test: 'cool' } }
      }
  }

console .log (convertUntil (x => x.test === "cool", myObj))

Но следите за -

console .log (convertUntil (x => x.test === "cool", { test: "cool" }))
// [ { id: "test", label: "test", children: "cool" } ]

фиксированная глубина

Другой вариант будетпреобразовать вложенную структуру в указанную depth -

const identity = x =>
  x

const convert = (o = {}, depth = 0) =>
  Object (o) !== o
    ? o
: Object
    .entries (o)
    .map
      ( ([ k, v ]) =>
          depth === 0 // <-- depth test
            ? { id: k, label: k } // <-- no children
            : { id: k, label: k, children: convert (v, depth - 1) } // <-- depth minus one
      )

const myObj =
  { parent1:
      { child1: { lastChild1: { test: 'cool' } }
      , child2: { lastChild2: { test: 'cool' } }
      }
  , parent2:
      { child2_1: { lastChild2_1: { test: 'cool' } }
      , child2_2: { lastChild2_2: { test: 'cool' } }
      }
  }

// show various depths
for (const d of [ 0, 1, 2 ])
  console .log (`depth: ${d}`, convert (myObj, d))

комбинированная техника

Согласно комментарию Скотта, техники могут быть объединены в одно решение. Это позволяет пользователю продолжать преобразование на основе свойств объекта или указанного depth уровня -

const identity = x =>
  x

const convertUntil = (test = identity, o = {}, depth = 0) =>
  Object (o) !== o
    ? o
: Object
    .entries (o)
    .map
      ( ([ k, v ]) =>
          test (v, depth) // <-- include depth in test
            ? { id: k, label: k }
            : { id: k, label: k, children: convertUntil (test, v, depth + 1) } // <-- depth plus one
      )

const myObj =
  { parent1:
      { child1: { lastChild1: { test: 'cool' } }
      , child2: { lastChild2: { test: 'cool' } }
      }
  , parent2:
      { child2_1: { lastChild2_1: { test: 'cool' } }
      , child2_2: { lastChild2_2: { test: 'cool' } }
      }
  }

console .log (convertUntil ((_, depth) => depth === 2, myObj))
2 голосов
/ 04 октября 2019

const myObj = {
        parent1: {
            child1: {
                lastChild1: { test: 'cool'}
            },
            child2: {
                lastChild2: { test: 'cool'}
            }
        },
        parent2: {
            child2_1: {
                lastChild2_1: { test: 'cool'}
            },
            child2_2: {
                lastChild2_2: { test: 'cool'}
            }
        }
    }


function getOptions(obj) {
    return Object.keys(obj).reduce((acc, cur) => {
        acc.push({
          id: cur,
          label: cur,
          children: recurseList(obj[cur])
        })
        return acc;
    }, [])
}
  
function recurseList(obj) {
    return Object.keys(obj).reduce((acc, cur) => {
        if(obj[cur] instanceof Object) {
            let data = {
                id: cur,
                label:cur
            }      
            const children = recurseList(obj[cur]);
            If(children.length) {
                  data.children = children
            }
            acc.push(data)
        }
        return acc;
    }, [])
}

var result = getOptions(myObj)
console.log('result', result)

Проблема в том, что в цикле всегда используется пустой массив children. А также вы не используете свой самый первый key parent1 для отправки в ваш массив результатов.

1 голос
/ 04 октября 2019

Если вам не нужно отличать узел test от других узлов, то я думаю, что это просто:

const convert = (obj) =>
  Object .entries (obj) .map (([k, v]) => ({
    id: k, 
    label: k,
    ...(typeof v == 'object' ? {children: convert (v)} : {})
  }))

const myObj = {
  parent1: {child1: {lastChild1: { test: 'cool'}}, child2: {lastChild2: { test: 'cool'}}},
  parent2: {child2_1: {lastChild2_1: { test: 'cool'}}, child2_2: {lastChild2_2: { test: 'cool'}}}
}

console .log (
  convert (myObj)
)

Я предполагаю, что разграничение этого самого глубокого узла сделает это значительно более сложным.

Обновление

Хорошо, так что это не такГораздо сложнее, если условие состоит в том, что объект не имеет свойств, которые сами являются объектами. (Это все еще не ясно, и запрошенный выходной образец и изображение, опубликованное в качестве комментария к другому ответу, похоже, не согласны. Но это было бы моим лучшим предположением.) Мы можем сделать это с помощью встроенного теста:

const convert = (obj) =>
  Object .entries (obj) .map (([k, v]) => ({
    id: k, 
    label: k,
    ...((typeof v == 'object' && Object .values (v) .some (o => typeof o == 'object'))
         ? {children: convert (v)} 
         : {}
       )
  }))

или с помощью вспомогательной функции:

const hasObjectProperties = (obj) =>
  Object .values (obj) .some (o => typeof o == 'object')

const convert = (obj) =>
  Object .entries (obj) .map (([k, v]) => ({
    id: k, 
    label: k,
    ...((typeof v == 'object' && hasObjectProperties(v))
         ? {children: convert (v)} 
         : {}
       )
  }))

При использовании последнего код становится:

const hasObjectProperties = (obj) =>
  Object .values (obj) .some (o => typeof o == 'object')

const convert = (obj) =>
  Object .entries (obj) .map (([k, v]) => ({
    id: k, 
    label: k,
    ...((typeof v == 'object' && hasObjectProperties(v))
         ? {children: convert (v)} 
         : {}
       )
  }))

const myObj = {
  parent1: {child1: {lastChild1: { test: 'cool'}}, child2: {lastChild2: { test: 'cool'}}},
  parent2: {child2_1: {lastChild2_1: { test: 'cool'}}, child2_2: {lastChild2_2: { test: 'cool'}}}
}

console .log (
  convert (myObj)
)
...