Проблемы с реализацией объектного итератора для больших вложенных объектов

#javascript

#javascript

Вопрос:

Представьте, что есть объект, и мы пытаемся написать функцию, которая принимает ‘path’ в качестве параметра и печатает все, что находится внутри него. Если входные данные неверны, выдайте ошибку. Размер объекта может быть огромным.

 const obj = {
  test: {
    demo: [{
      lname: 'dave'
    }]
  }
};

function getData(obj, dest) {
  const path = dest.split('.');
  return helper(obj, path);

  function helper(obj, path) {
    if (!path.length) return obj;
    const cur = path.shift();

    if ((Array.isArray(obj) amp;amp; typeof obj === 'string') ||
      (typeof obj === 'undefined')) {
      throw new Error("Something wrong")
    }

    obj = obj[cur];
    return helper(obj, path);
  }
}

console.log(getData(obj, 'test.demo.0.lname'));
//console.log(getData(obj, 'test.demo.dave.lname')); // throws an error since in demo array you can't access 'dave'  

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

Комментарии:

1. Вы могли бы написать это в паре строк, но вам пришлось бы пропустить проверки, которые вы выполняете в данный момент, поэтому запуск getData({a: 1}, "a.b" )` может завершиться ошибкой по-разному.

2. Конечно, но можете ли вы дать мне сокращенную версию?

3. Хорошо, я дам вам ответ. Однако, один момент — когда это условие будет выполнено? (Array.isArray(obj) amp;amp; typeof obj==='string')

4. Array.isArray(obj) amp;amp; typeof obj==='string' Это условие никогда не будет выполнено true , потому что Array.isArray в основном эквивалентно Object.prototype.toString.call(obj) === "[object Array]" , где строка выдаст "[object String]"

5. @VLAZ что, если мой ввод console.log(getData(obj, 'test.demo.dave.lname')); тогда он сломается

Ответ №1:

Простая и короткая реализация может использовать Array#reduce для итеративного извлечения ключей из объекта следующим образом:

 const obj = {
  test: {
    demo: [{
      lname: 'dave'
    }]
  }
};

function getData(obj, dest) {
  var keys = dest.split(".");
  return keys.reduce(function(currentObject, key) {
    if(typeof currentObject == "undefined") throw Error("Something wrong");
    return currentObject[key];
  }, obj)
}

console.log(getData(obj, 'test.demo.0.lname'));
console.log(getData(obj, 'test.demo.dave.lname')); // throws an error since in demo array you can't access 'dave'  

Это более подробный вариант, чтобы продемонстрировать, что происходит, вы можете сократить его еще больше

 const obj = {
  test: {
    demo: [{
      lname: 'dave'
    }]
  }
};

function getData(obj, dest) {
  return dest.split(".").reduce((curr, key) => curr[key], obj)
}

console.log(getData(obj, 'test.demo.0.lname'));
console.log(getData(obj, 'test.demo.dave.lname')); // throws an error since in demo array you can't access 'dave'  

Вы также можете избежать ошибок, а просто вернуть undefined , если ключ не найден

 const obj = {
  test: {
    demo: [{
      lname: 'dave'
    }]
  }
};

function getData(obj, dest) {
  return dest.split(".").reduce((curr, key) => curr != undefined ? curr[key] : undefined, obj)
}

console.log(getData(obj, 'test.demo.0.lname'));
console.log(getData(obj, 'test.demo.dave.lname')); // undefined  

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

Если вы используете Lodash, то вы можете использовать их _.get, который еще более надежен и обрабатывает больше синтаксиса

 const obj = {
  test: {
    demo: [{
      lname: 'dave'
    }]
  }
};

console.log(_.get(obj, 'test.demo.0.lname'));
console.log(_.get(obj, 'test.demo[0].lname'));
console.log(_.get(obj, ['test', 'demo', 0, 'lname']));

console.log(_.get(obj, 'test.demo.dave.lname')); // undefined

console.log(_.get(obj, 'test.demo.dave.lname', 'this is not dave but the default vale'));  
 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.11/lodash.min.js"></script>  

Комментарии:

1. Почему это не удается для ` const obj2 = { keys: { lists: [{ FirstName: ‘dave’ }] } }; `

2. Какой ключ вы пытаетесь получить от него? И видите — это проблема с отладкой, о которой я упоминал.

3. Я пытаюсь получить obj2.keys.lists.0.firstName

4. Это работает … если вы вызываете его как getData(obj2, "keys.lists.0.firstName") — если вы попытались getData(obj2, "obj2.keys.lists.0.firstName") , то это не сработает, потому что нет obj2 ключа.

Ответ №2:

Если можно использовать Lodash, то:

 if(!_.has(obj,path)){
  //bad
}else{
  let val = _.get(obj,path);
}
  

Если вас не волнует, существует ли какой-либо путь или он имеет undefined значение, тогда вы можете просто сделать:

 let val = _.get(obj,path);
if(val===undefined){
  //bad
}
  

Если вас волнуют разреженные массивы и «пустые слоты», то не полагайтесь на _.has метод, потому что он возвращает true пустой слот.
Вместо этого вы можете использовать альтернативный _.exists метод из расширения Deepdash для Lodash.
Этот будет аккуратно возвращать false для пустых слотов.