将嵌套对象数组扩展为路径数组的最快方法(虚线)

时间:2018-11-09 14:44:41

标签: javascript json lodash

我有一个查询语法,需要将其应用于json对象并在json对象中返回有效路径的数组。

例如,具有这样的查询:

People.[].Dependents.[]

以及以下JSON对象:

{
    "People": [
        {
            "FirstName": "John",
            "LastName": "Doe",
            "Dependents": [
                {
                    "Name": "John First Dep"
                },
                {
                    "Name": "John Second Dep"
                }
            ]
        },
        {
            "FirstName": "Jane",
            "LastName": "Smith",
            "Dependents": [
                {
                    "Name": "Jane First Dep"
                }
            ]
        }
    ]
}

结果将是:

[
    "People.0.Dependents.0",
    "People.0.Dependents.1",
    "People.1.Dependents.0",
]

我目前正试图尽可能简洁地做到这一点。到目前为止,我所做的任何尝试都导致了太多的代码,并且难以遵循。我缺少明显的东西吗?

编辑:当前代码:

function expandQuery(data, path) {
    const parts = path.split("[]").map(s => _.trim(s, "."));
    const [outer, ...right] = parts;

    const inner = _.join(right, ".[].");

    let groupData = _.get(data, outer, []);
    if (!_.isArray(groupData)) {
        groupData = [groupData];
    }
    const groupLength = groupData.length;

    let items = [];
    for (let ind = 0; ind < groupLength; ind++) {
        items.push(outer + "." + ind.toString() + "." + inner);
    }

    const result = [];

    for (let ind = 0; ind < items.length; ind++) {
        const item = items[ind];
        if (item.includes("[]")) {
            result.push(...expandQuery(data, item));
        } else {
            result.push(_.trim(item, "."));
        }
    }
    return result;
}

我正专门将其缩短。

4 个答案:

答案 0 :(得分:2)

这可以满足您的要求,但是比您的解决方案简单/短得多。

function getPaths (collection, query) {
    let tokens = query.split(".")

    function walkPath (collection, [currentToken, ...restTokens], paths) {
        if (!currentToken) { // no more tokens to follow
            return paths.join(".")
        }
        if (currentToken === "[]") { // iterate array
            const elemPaths = _.range(collection.length)
            return elemPaths.map(elemPath => walkPath(collection[elemPath], restTokens, [...paths, elemPath]))
        }
        else {
            return walkPath(collection[currentToken], restTokens, [...paths, currentToken])
        }
    }

    return _.flattenDeep(walkPath(collection, tokens, []))
}

它也缺少错误处理。
也许这对您有用。

答案 1 :(得分:0)

要展平深度嵌套的数组,可以使用_.flattenDeep。 但是,如果您不介意离开lodash,则可以执行以下操作:

function getNextToken(path) {
  let token = path.trim();
  let rest = '';

  const separatorPos = path.indexOf('.');
  if (separatorPos > -1) {
    token = path.substr(0, separatorPos).trim();
    rest = path.substr(separatorPos + 1).trim();
  }

  return { token, rest };
}


const expandQuery = (data, path) => {
  const expand = (data, path, found = []) => {
    if (data === undefined) {
      return [];
    }

    const { token, rest } = getNextToken(path);
    switch (token) {
      // Got to the end of path
      case '':
        return [found.join('.')];

      // Got an array
      case '[]':
        if (data.constructor !== Array) {
          return [];
        }

        const foundPaths = [];
        let i;
        for (i = 0; i < data.length; ++i) {
          // Flatten out foundPaths
          foundPaths.push.apply(
            foundPaths,
            expand(data[i], rest, [...found, i])
          );
        }
        return foundPaths;

      // Got a property name
      default:
        return expand(data[token], rest, [...found, token]);
    }
  };
  
  return expand(data, path);
};

const query = 'People.[].Dependents.[]';
const input = {
  "People": [{
      "FirstName": "John",
      "LastName": "Doe",
      "Dependents": [{
          "Name": "John First Dep"
        },
        {
          "Name": "John Second Dep"
        }
      ]
    },
    {
      "FirstName": "Jane",
      "LastName": "Smith",
      "Dependents": [{
        "Name": "Jane First Dep"
      }]
    }
  ]
};
console.log(expandQuery(input, query));

可能不是最短的,但是它检查数据类型。同样null个值也被认为是一个匹配项。如果您也想忽略它们,则可以检查是否data === null

答案 2 :(得分:0)

另一种方法:)

var _ = require('lodash');
var test = {
    "People": [
        {
            "FirstName": "John",
            "LastName": "Doe",
            "Dependents": [
                {
                    "Name": "John First Dep"
                },
                {
                    "Name": "John Second Dep"
                }
            ]
        },
        {
            "FirstName": "Jane",
            "LastName": "Smith",
            "Dependents": [
                {
                    "Name": "Jane First Dep"
                }
            ]
        }
    ]
}

function mapper(thing, prefix, paths) {
    if (_.isObject(thing)) {
        _.forEach(thing, function(value, key) {mapper(value, prefix+key+'.', paths);});
    } else if (_.isArray(thing)) {
        for (var i = 0; i < thing.length; i++) mapper(value, prefix+i+'.', paths);
    } else {
        paths.push(prefix.replace(/\.$/, ''));
    }
}

var query = 'People.[].Dependents.[]';
var paths = [];
var results;

query = new RegExp(query.replace(/\[\]/g,'\\d'));
mapper(test, '', paths); // Collect all paths
results = _.filter(paths, function(v) {return query.test(v);}); // Apply query
console.log(results);

答案 3 :(得分:0)

我也会试一试,请注意,当您使用_.get(object,'path.with.point')并且对象键名中包含点时,您的代码将被破坏,我更喜欢使用_.get(object,['path','with','point'])

const data = {"People":[{"FirstName":"John","LastName":"Doe","Dependents":[{"Name":"John First Dep","a":[1,2]},{"Name":"John Second Dep","a":[3]}]},{"FirstName":"Jane","LastName":"Smith","Dependents":[{"Name":"Jane First Dep","a":[1]}]}]};

const flatten = arr =>
  arr.reduce((result, item) => result.concat(item))
const ARRAY = {}
const getPath = (obj, path) => {
  const recur = (result, path, item, index) => {
    if (path.length === index) {
      return result.concat(path)
    }
    if (item === undefined) {
      throw new Error('Wrong path')
    }
    if (path[index] === ARRAY) {
      const start = path.slice(0, index)
      const end = path.slice(index + 1, path.length)
      return item.map((_, i) =>
        recur(result, start.concat(i).concat(end), item, index)
      )
    }
    return recur(result, path, item[path[index]], index + 1)
  }
  const result = recur([], path, obj, 0)
  const levels = path.filter(item => item === ARRAY).length - 1
  return levels > 0
    ? [...new Array(levels)].reduce(flatten, result)
    : result
}

console.log(
  getPath(data, ['People', ARRAY, 'Dependents', ARRAY, 'a', ARRAY])
)