Skip to content

Instantly share code, notes, and snippets.

@VoloshchenkoAl
Last active October 5, 2021 15:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VoloshchenkoAl/e3bef6e66d3eef4bc81a3246dd027061 to your computer and use it in GitHub Desktop.
Save VoloshchenkoAl/e3bef6e66d3eef4bc81a3246dd027061 to your computer and use it in GitHub Desktop.

Многие увидев #Задачка_28 — "о, так здесь же можно просто взять lodash.get". И были бы правы. Но сначала рассмотрим более креативные варианты решений

Первый вариант

Решение на позицию тех. лида 🙂

Написать конвертор путей в формат json-pointer. А потом использовать библиотеку, которая умеет доставать значения с объекта в новом формате.

Сылка на формат json-pointer— https://datatracker.ietf.org/doc/html/rfc6901

Спасибо Юре Ткаченко, мы сами про это не подумали.

Ну а если вас рассматривают на позицию человека работающего руками, то ждут что-то вроде следующих вариантов.

Второй вариант

Почти самое краткое решение от Максима

function getWithPath(obj, propertyPath){
  try {
    with (obj){
      return eval(propertyPath);
    }
   } catch(e) {}
}

Но есть проблема — eval. Вернее даже сказать, что это роковая ошибка, выстрел в колено, шаг в бездну (нужное подчеркнуть). Как нибудь сделаем про это отдельную задачку, а пока просто не используйте eval в продакшене никогда.

Если знаете как решить еще короче – пишите в комменты.

Третий вариант

Рекурсивное решение от Станислава

function getWithPath(obj, path) {
  // some edge cases
  if (obj === null || obj === undefined) return obj;
  // empty path, need to return the value
  if (path === '') return obj;
  const [current, ...steps] = path.split('.');

  if (current.includes('[')) {
    // it feels like property is an array
    const openBracketPos = current.indexOf('[');
    const closeBracketPos = current.indexOf(']');
    // is it one-dimensional array?
    const key = current.substring(0, openBracketPos);
    // need to check for correctness, e.g. key is a valid key, indexes bounds
    // not implemented through 
    const idx = parseInt(current.substring(openBracketPos + 1, closeBracketPos));
    // here should be NaN check
    if (key) {
      // take a value from current key and call itself with next steps
      return getWithPath(obj[key][idx], current.substring(closeBracketPos + 1) + steps.join('.'));
    } else {
      // thats an multidimensional array, lets unwind it
      return getWithPath(obj[idx], current.substring(closeBracketPos + 1) + steps.join('.'));
    }
  } else {
    // this is a regular property
    // take a value from current key and call itself with next steps
    return getWithPath(obj[current], steps.join('.'));
  }
}

Четвертый вариант

Заменяем рекурсию на цикл. Смотрите сами:

const getWithPath = (object: Object, path: string) => {
  return path.split('.').reduce((acc, currentPath) => {
    if (!acc) return acc;

    const property = currentPath.split('[')[0];
    const indexes = currentPath.match(/(?<=\[).+?(?=])/g) ?? [];
    return indexes.reduce((acc, i) => acc && acc[i], acc[property]);
  }, object);
};

Обратите внимание, как круто достаём индексы массива с помощью регулярного выражения:

/(?<=\[).+?(?=])/g

Вариант очень хороший, его прислал Женя.

А теперь вернемся к lodash.get 🙃

Давайте посмотрим алгоритм для идеального случая:

  1. Преобразуем строку path в масив путей (https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L3029)
path = castPath(path, object);
  1. Итеративно пройдемся по массиву путей, доставая значение вложеных объектов (https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L3034)
    var index = 0,
        length = path.length;
    
    while (object != null && index < length) {
      object = object[toKey(path[index++])];
    }
  2. Вернем значение, если оно есть. В противном случае — undefined (https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L3037)
return (index && index == length) ? object : undefined;

Как вы могли заметить, самое интересное — этап преобразования. Каким образом он реализован? В его основе лежит функция replace и магический regex.(https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L6740)

var result = [];
var rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
var reEscapeChar = /\\(\\)?/g;

string.replace(rePropName, function(match, number, quote, subString) {
  result.push(quote ? subString.replace(reEscapeChar, '$1') : (number || match));
});

Если посмотреть внимательно, то можно многое узнать о методе replace. Например:

Но есть ли альтернативный вариант преобразование path? Самое время ещё раз передать привет подписчикам и привести их примеры:

  • Саша решил сделать массив путей через split. Используя регулярное выражение и функцию filter:
    path = path.split(/[.\[\]]/).filter(Boolean);
  • Виталик заменил символы "[" , "]" на точки и сделал split:
    path = path.replace(/[\[\]\.]+/g, ".").replace(/\.$/g, "").split('.')

Так и закончим разбор. Спасибо, что присылаете свои варианты решений

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment