Получить пути самых глубоких свойств в объекте

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

{
    userName: [],
    email: [],
    name: {
        fullName: [],
        split: {
            first: [],
            last: []
        }
    },
    date: {
        input: {
            split: {
                month: [],
                year: []
            },
            full: []
        },
        select: {
            month: [],
            year: []
        }
    }
};

Мне нужен массив, содержащий что-то вроде:

["userName", "email", "name.fullName", "name.split.first",...]

Существуют ли какие-либо встроенные или внешние библиотеки, которые делают это автоматически? Я пытался использовать Object.keys для родительского объекта, но это возвращает только прямые дочерние свойства.


person Noam Suissa    schedule 03.09.2019    source источник


Ответы (3)


Вы можете использовать Array.prototype.flatMap для этого -

const d =
  {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}

const main = (o = {}, path = []) =>
  Array.isArray(o) || Object(o) !== o
    ? [ path ]
    : Object
        .entries(o)
        .flatMap(([ k, v ]) => main(v, [...path, k ]))
        
console.log(main(d))

Выход

[ [ "userName" ]
, [ "email" ]
, [ "name", "fullName" ]
, [ "name" ,"split", "first" ]
, [ "name", "split", "last" ]
, ...
]

Если вы хотите, чтобы пути были "a.b.c" вместо [ "a", "b", "c" ], используйте .map и Array.prototype.join -

const d =
  {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}

const main = (o = {}, path = []) =>
  Array.isArray(o) || Object(o) !== o
    ? [ path ]
    : Object
        .entries(o)
        .flatMap(([ k, v ]) => main(v, [...path, k ]))
        
console.log(main(d).map(path => path.join(".")))

Выход

[
  "userName",
  "email",
  "name.fullName",
  "name.split.first",
  "name.split.last",
  "date.input.split.month",
  "date.input.split.year",
  "date.input.full",
  "date.select.month",
  "date.select.year"
]

If you do not want to rely on Array.prototype.flatMap because it is not supported in your environment, you can use a combination of Array.prototype.reduce and Array.prototype.concat -

const d =
  {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}

const main = (o = {}, path = []) =>
  Array.isArray(o) || Object(o) !== o
    ? [ path ]
    : Object
        .entries(o)
        .reduce // <-- manual flatMap
          ( (r, [ k, v ]) =>
              r.concat(main(v, [...path, k ]))
          , []
          )
        
console.log(main(d).map(path => path.join(".")))


Или вы можете полифилл Array.prototype.flatMap -

Array.prototype.flatMap = function (f, context)
{ return this.reduce
    ( (r, x, i, a) => r.concat(f.call(context, r, x, i, a))
    , []
    )
}

Есть ли способ получить доступ к значению любого из этих свойств? Например. "d.name.split.first" используя возвращенный массив в позиции 3?

Мы можем написать функцию поиска, которая принимает объект, o, и строку, разделенную точками, s, которая возвращает значение, если это возможно, иначе возвращает undefined, если s недостижимо -

const d =
  {userName:[],email:[],name:{fullName:[],split:{first:[],last:[]}},date:{input:{split:{month:[],year:[]},full:[]},select:{month:[],year:[]}}}
  
const lookup = (o = {}, s = "") =>
  s
    .split(".")
    .reduce
      ( (r, x) =>
          r == null ? undefined : r[x]
      , o
      )
 
console.log(lookup(d, "name.split"))
// { first: [], last: [] }

console.log(lookup(d, "name.split.first"))
// []

console.log(lookup(d, "name.split.zzz"))
// undefined

console.log(lookup(d, "zzz"))
// undefined

person Mulan    schedule 03.09.2019
comment
и поддержка ограничена developer.mozilla. org/en-US/docs/Web/JavaScript/Reference/ - person epascarello; 04.09.2019
comment
Я бы не назвал поддержку ограниченной только потому, что она недоступна примерно для 5%. Может быть, он даже не используется в браузере? Разработчики JS, создающие JS для Интернета, знакомы (или должны будут познакомиться) с необходимостью полифилла. Должно ли это обсуждение происходить по каждому вопросу JS? - person Mulan; 04.09.2019
comment
Просто отметьте это, чтобы пользователь не пошел и не использовал его вслепую. - person epascarello; 04.09.2019
comment
@ user633183 Большое спасибо. Есть ли способ получить доступ к значению любого из этих свойств, используя приведенное выше решение для записи через точку? Например. d.name.split.first используя возвращенный массив в позиции 3? - person Noam Suissa; 04.09.2019
comment
@NoamSuissa, кажется, я понимаю, о чем ты спрашиваешь. Я обновил ответ. Лайк, если это то, что ты ищешь ^^ - person Mulan; 04.09.2019
comment
я впечатлен, у этого ответа был только 1 голос, это круто. Моя единственная проблема сейчас заключается в том, как обрабатывать массив объектов, любая идея была бы потрясающей, даже сохранение первого элемента в качестве значения свойства было бы хорошо. - person llermaly; 20.06.2021
comment
@llermaly У меня есть несколько других ответов, которые уточняют и совершенствуют технику в этом ответе, каждый из которых обрабатывает объекты и массивы любого уровня вложенности. См. пример один и два и три. Если у вас есть вопросы, я буду рад помочь. - person Mulan; 20.06.2021

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

var myObj = {
    userName: [],
    email: [],
    name: {
        fullName: [],
        split: {
            first: [],
            last: []
        }
    },
    date: {
        input: {
            split: {
                month: [],
                year: []
            },
            full: []
        },
        select: {
            month: [],
            year: []
        }
    }
}

function setPath(a, b) {
  return a.length ? a + '.' + b : b
}

function getAllPaths(obj, paths, currentPath) {
  if (paths===undefined) paths = []
  if (currentPath===undefined) currentPath = ''
  Object.entries(obj).forEach( function (entry) {
    const updatedPath = setPath(currentPath, entry[0])
    if (entry[1] instanceof Object && !Array.isArray(entry[1])) { 
      getAllPaths(entry[1], paths, updatedPath)
    } else {
      paths.push(updatedPath)
    }
  })
  return paths
}


console.log(getAllPaths(myObj))

написано со стрелочными функциями и значениями по умолчанию

var myObj = {
    userName: [],
    email: [],
    name: {
        fullName: [],
        split: {
            first: [],
            last: []
        }
    },
    date: {
        input: {
            split: {
                month: [],
                year: []
            },
            full: []
        },
        select: {
            month: [],
            year: []
        }
    }
}

const setPath = (a, b) => a.length ? a + '.' + b : b

const getAllPaths = (obj, paths=[], currentPath='') => {
  Object.entries(obj).forEach( ([key, value]) => {
    const updatedPath = setPath(currentPath, key)
    if (value instanceof Object && !Array.isArray(value)) { 
      getAllPaths(value, paths, updatedPath)
    } else {
      paths.push(updatedPath)
    }
  })
  return paths
}


console.log(getAllPaths(myObj))

person epascarello    schedule 03.09.2019

Ну же. Массивы и объекты в основном одно и то же. Нет абсолютно никакой необходимости в неопределенных проверках.

data = { ... };

function paths_list( value, result=[], path=[] )
{
    for ( keydx in value )
    {
        if ( value[keydx] instanceof Object )
        {
            path.push( keydx );
            result.push( path.join(".") );
            paths_list( value[keydx], result, path );
            path.pop();
        }
    }

    return result;
}

console.log( paths_list(data) );

Отпечатки

Массив ["userName", "email", "name", "name.fullName", "name.split", "name.split.first", "name.split.last", "date", "date.input ", "date.input.split", "date.input.split.month", "date.input.split.year", "date.input.full", "date.select", "date.select.month ", "дата.выберите.год"]

person Holli    schedule 03.09.2019
comment
Гениальное решение ???? ???? - person masoudmanson; 04.09.2019
comment
@masoudmanson Когда дело доходит до рекурсии, я предпочитаю все упрощать, потому что иначе мой мозг начинает таять ;-) Но ты. - person Holli; 04.09.2019
comment
@Holli Холли, я не совсем понимаю причину этого, но большое спасибо за решение - person Noam Suissa; 04.09.2019
comment
Это было адресовано не вам, а моим коллегам-ответчикам ;-) - person Holli; 04.09.2019