【问题标题】:How to implement glob ** for an object?如何为对象实现 glob **?
【发布时间】:2017-05-24 09:05:15
【问题描述】:

如何支持 globstar 风格的 ** 搜索 JavaScript 对象?

这与 lodash 的 _.get 类似,只是要查询的对象可以包含这些特殊的 *** 属性,如果没有完全匹配,它们将被匹配。

在下面的示例代码中,{ **: { middleName: 3 }} 规则应与以下路径匹配:

['middleName']
['clients', 'middleName']
['clients', '0', 'middleName']

因为**匹配0个或多个元素,所以{**: { middleName: x }}本质上匹配任何以'middleName'结尾的数组并返回x

但是,由于有一个* 规则,它只匹配一个级别(恰好1 个元素具有任何值),它应该优先于**,导致第二条路径改为评估为{ lastName: 2 }

在实践中,我不太关心优先级,因为不应该编写重叠的“规则”,但无论如何我都不知道如何实现**

如果您需要一个更实际的示例,我打算将其用于:1、2、3 数字将替换为验证器函数,“路径”将是 <input> 名称。通过这种方式,我可以为我的表单指定验证规则。

另外,我真的只想返回叶节点,所以其中一个返回 { lastName: 2 } 的事实有点奇怪,但这是我不太关心的边缘情况。如果** 优先于此,因为它是叶节点匹配,我想那会更好。

代码:

function glob(object, path, defaultValue) {
  let [first, ...rest] = path;
  let value = object[first];
  if (value === undefined) {
    value = object['*'];
    if (value === undefined) {
      // TODO: support **
      return defaultValue;
    }
  }
  if (rest.length) {
    return glob(value, rest, defaultValue);
  }
  return value;
}

let rules = {
  firstName: 1, // matches ['firstName']
  clients: {
    '*': {
      lastName: 2, // matches ['clients', anything, 'lastName']
    }
  },
  '**': {
    middleName: 3, // matches [...anything, 'middleName']
  }
}

console.log(glob(rules, ['firstName'])); // 1
console.log(glob(rules, ['clients', '0', 'lastName'])); // 2
console.log(glob(rules, ['clients', '0', 'middleName'])); // should be 3
console.log(glob(rules, ['clients', 'middleName'])); // should be { lastName: 2 } or 3, whatever's easier to implement
console.log(glob(rules, ['middleName'])); // should be 3

【问题讨论】:

  • "如何支持 globstar 风格的 ** 通过 JavaScript 对象进行搜索?"你不是在搜索字符串数组(你称之为“路径”)吗?问题陈述可能不那么令人困惑。
  • @spinkus 是的,我知道这有点令人困惑,这就是我包含示例的原因。我不是在搜索数组/路径——路径被用作对象的键。这就像 lodash 的 _.get 一样,只是对象可以包含这些特殊的 *** 键。

标签: javascript search recursion glob


【解决方案1】:

问题看起来很像文件路径通配。代码中的规则对象似乎有点奇怪。不知道你是如何结束它的,或者它更广泛的目的可能是什么,但是......我认为你可以将你的规则从一个对象重构为全局路径。有很多用于通配符的 JS 包。以下是使用 minimatch globber 的重构(在原始列表中添加了一条规则,以显示如何使用 glob 表达式减少冗余)。

var minimatch = require("minimatch")
var _ = require('underscore')

rules = {
  'firstName': 1,
  'clients/*/lastName': 2,
  '**/middleName': 3,
  'people/*/+(middleName|lastName)': 4,
}
rule_keys = _.keys(rules).sort().reverse() // Force a well defined match order.

function glob(rules, path) {
  path = path.join('/')
  for(r of rule_keys) {
    if(minimatch(path, r)) {
      return rules[r];
    }
  }
}

paths = [
  [['firstName'], "1"],
  [['clients', '0', 'lastName'], "2"],
  [['clients', '0', 'middleName'], "should be 3"],
  [['clients', 'middleName'], "should be 2|3"],
  [['middleName'], "should be 3"],
  [['people', '0', 'middleName'], "should be 4"],
];

for(p in paths) {
  console.log(glob(rules, paths[p][0]), paths[p][1]);
}

确实,minimatch 实现可能会将字符串表达式解释为与您展示的对象类似的对象 - 除了更复杂,因为它支持更多功能。

【讨论】:

  • 是的,它类似于文件路径通配,这就是我将其称为通配的原因 :-) 规则格式只是您所拥有的内容的嵌套版本,这将减少一点冗余因为我添加了很多规则,并且我使用.s 作为分隔符而不是/s,因为它们实际上不是文件路径,它是一个对象路径,并且点在 JS 和其他编程语言中更常见为了那个原因。我没有考虑过实际使用 minimatch ——我想我必须禁用一堆选项才能让它按我想要的方式工作,但它可以工作。不过,我不喜欢改变我的语法。
  • @mpen “规则格式是......将减少冗余,因为我添加了很多规则”。 minimatch 支持大括号和“extglob”(如 +(a|b))扩展以减少冗余。这些扩展可能比使用对象格式更好地减少冗余不是吗?更新了答案以显示示例。
  • 属性值可能会有所不同。用一个规则来匹配两者并不理想。
  • 啊,明白你的意思了。不理想,虽然简单:)。将澄清将这种情况添加到问题中的示例规则中。
猜你喜欢
  • 2010-09-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
相关资源
最近更新 更多