【问题标题】:Fastest way to expand nested object array to array of paths (lodash)将嵌套对象数组扩展为路径数组的最快方法(lodash)
【发布时间】:2018-11-09 14:44:41
【问题描述】:

我有一个查询语法,它需要应用于 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;
}

我正在特别想缩短这个时间。

【问题讨论】:

  • 到目前为止你的尝试是什么?
  • @lumio:添加了一个工作示例

标签: javascript json lodash


【解决方案1】:

这可以满足您的需求,但并不比您的解决方案更简单/更短。

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, []))
}

它也缺乏错误处理。
也许这对你有点用处。

【讨论】:

  • 哦...除了_.range,我喜欢这个解决方案! :)
  • 这比我提供的解决方案快一个数量级,也比其他答案快一点。 (也是最短的!)
  • @lumio:你为什么不喜欢_.range?这对我来说似乎真的很明智(作为一个来自 C#-with-linq 背景的人)
  • @caesay 因为它生成一个数组并在一个空数组上使用.map。最好在这里写一点for 循环(性能方面)
【解决方案2】:

要展平深度嵌套的数组,您可以使用_.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

【讨论】:

    【解决方案3】:

    另一个:)

    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);
    

    【讨论】:

      【解决方案4】:

      我也会试一试,请注意,当您使用 _.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])
      )

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-07-12
        • 1970-01-01
        • 2021-04-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-11
        • 1970-01-01
        相关资源
        最近更新 更多