【问题标题】:ES6 / lodash - check nested Object values with startsWithES6 / lodash - 使用startsWith检查嵌套对象值
【发布时间】:2017-01-20 06:27:24
【问题描述】:

是否有任何函数或任何快速方法来检查我们的对象中的某些值是否以开头,例如 asd

例子:

let obj = { 
   'child' : {
      'child_key': 'asdfghhj'
    },
    'free': 'notasd',
    'with': 'asdhaheg'
  }

// check here if our obj has value that startsWith('asd')

问候

【问题讨论】:

  • 你期望什么输出?一个布尔值?
  • 是的,它可以是布尔值。其实没关系,但那是最好的
  • 对象总是这个形状吗?
  • 我假设您的意思是进行深度搜索?还是您只搜索输入对象的值?
  • 深度搜索。具有更多嵌套属性和不同键的真实对象更大

标签: javascript object ecmascript-6 lodash


【解决方案1】:

如果您真的不关心匹配哪个节点/值,请使用@trincot 的解决方案。它直截了当,写得很好,并且非常有效地解决了您的问题。

如果您想要的不仅仅是一个布尔值作为您的挖掘结果,请继续阅读...


我真的怀疑你是否需要这个,但如果你的对象非常大,你会想要一个提前退出行为——这意味着一旦找到匹配项,迭代你的输入数据将停止,true/false 结果将立即返回。 @trincot 的解决方案提供提前退出,但使用 mapfilterreduce 的解决方案不提供此类行为。

findDeep 比仅仅检查一个字符串值是否以另一个字符串值开头更有用——它需要一个应用于数据中每个叶节点的高阶函数。

这个答案使用我的findDeep 过程来定义一个通用的anyStartsWith 过程,方法是检查findDeep 是否返回undefined(不匹配)

它适用于任何输入类型,它会遍历ObjectArray 子节点。

const isObject = x=> Object(x) === x
const isArray = Array.isArray
const keys = Object.keys
const rest = ([x,...xs]) => xs

const findDeep = f => x => {
  
  let make = (x,ks)=> ({node: x, keys: ks || keys(x)})
  
  let processNode = (parents, path, {node, keys:[k,...ks]})=> {
    if (k === undefined)
      return loop(parents, rest(path))
    else if (isArray(node[k]) || isObject(node[k]))
      return loop([make(node[k]), make(node, ks), ...parents], [k, ...path])
    else if (f(node[k], k))
      return {parents, path: [k,...path], node}
    else
      return loop([{node, keys: ks}, ...parents], path)
  }
  
  let loop = ([node,...parents], path) => {
    if (node === undefined)
      return undefined
    else
      return processNode(parents, path, node)
  }
  
  return loop([make(x)], [])
}

const startsWith = x => y => y.indexOf(x) === 0
const anyStartsWith = x => xs => findDeep (startsWith(x)) (xs) !== undefined

let obj = { 
  'child' : {
    'child_key': 'asdfghhj'
  },
  'free': 'notasd',
  'with': 'asdhaheg'
}

console.log(anyStartsWith ('asd') (obj))   // true
console.log(anyStartsWith ('candy') (obj)) // false

你会发现这有点浪费findDeep 的潜力,但如果你不需要它的力量,那么它不适合你。

这是findDeep的真正力量

findDeep (startsWith('asd')) (obj)

// =>     
{
  parents: [
    {
      node: {
        child: {
          child_key: 'asdfghhj'
        },
        free: 'notasd',
        with: 'asdhaheg'
      },
      keys: [ 'free', 'with' ]
    }
  ],
  path: [ 'child_key', 'child' ],
  node: {
    child_key: 'asdfghhj'
  }
} 

生成的对象有 3 个属性

  • parents – 对匹配值沿袭中每个节点的完整对象引用
  • path – 获取匹配值的键路径(堆栈反转)
  • node – 匹配的键/值对

你可以看到,如果我们把父对象作为p,并把路径栈倒过来,我们就得到了匹配的值

p['child']['child_key']; //=> 'asdfghhj'

【讨论】:

  • 当您提到我的回答时,我想知道:您是否建议我提出的解决方案在找到匹配项时不会提前退出?
  • @trincot 不,一点也不。我会进行更新以更明确地说明这一点。
  • 啊,对不起,我的误会了……我又读了一遍,确实我在那里连接错误;-)
  • @trincot 我原来的答案没有提到你的。当我在顶部添加该位时,我没有足够小心地将我试图传达的两个不同的东西分开。
【解决方案2】:

这是一个使用 ES6 的函数:

function startsWithRecursive(obj, needle) {
    return obj != null && 
        (typeof obj === "object"
            ? Object.keys(obj).some( key => startsWithRecursive(obj[key], needle) )
            : String(obj).startsWith(needle));
}

// Sample data
let obj = { 
    'child' : {
      'child_key': 'asdfghhj'
    },
    'free': 'notasd',
    'with': 'asdhaheg'
};
// Requests
console.log( 'obj, "asd":',    startsWithRecursive(obj, 'asd'    ) );
console.log( 'obj, "hello":',  startsWithRecursive(obj, 'hello'  ) );
console.log( 'null, "":',      startsWithRecursive(null, ''      ) );
console.log( 'undefined, "":', startsWithRecursive(undefined, '' ) );
console.log( '"test", "te":',  startsWithRecursive('test', 'te'  ) );
console.log( '12.5, 1:',       startsWithRecursive(12.5, 1       ) );

说明:

该函数是递归的:它在遍历嵌套对象结构时调用自己。作为obj 传递的值可以属于以下三类之一:

  1. 相当于null(也像undefined):在这种情况下,既不能进行递归调用,也不能调用startsWith方法:结果是false,因为这样value 显然不是以给定的搜索字符串开头;

  2. 它是一个对象:在这种情况下,应该检查该对象的属性值。这将通过递归调用来完成。 some 方法确保一旦找到匹配项,迭代就会停止,并且不会检查进一步的属性值。在这种情况下,some 返回true。如果没有匹配的属性值,some 返回false

  3. 以上都不是。在这种情况下,我们将其转换为字符串(通过应用String 函数)并对其应用startsWith

在适用步骤中计算的值将作为函数结果返回。如果这是一个递归调用,它将被视为some 回调中的返回值,...等等。

请注意,当您在字符串上调用此函数时,它也会返回正确的结果,如下所示:

startsWithRecursive('test', 'te'); // true

非递归替代

在回答 cmets 关于潜在堆栈限制的问题时,这里有一个替代的非递归函数,它在变量中维护一个“堆栈”:

function startsWithRecursive(obj, needle) {
    var stack = [obj];
    while (stack.length) {
        obj = stack.pop();
        if (obj != null) {
            if (typeof obj === "object") {
                stack = stack.concat(Object.keys(obj).map( key => obj[key] ));
            } else {
                if (String(obj).startsWith(needle)) return true;
            }
        }
    }
    return false;
}

【讨论】:

  • 非常好,这适用于 Array 子节点,尽管我不确定这是故意的还是只是幸运的副作用 ^_^
  • 在 JavaScript 中,数组是对象 (typeof "object") 具有一些附加功能(如 length 属性),所以是的,它适用于数组。
  • 关于递归需要注意的一点,堆栈是有限的,对于非常大的对象,您可以达到限制。
  • @Xotic750 更具体地说,嵌套非常深的对象可能会达到极限。一个平面对象,甚至是具有数百个嵌套级别和数千个键的对象都可以。
  • 非常好。我并不是说这是必须的,只是一个说明。但是非常好。 ;) +1
【解决方案3】:

您可以递归迭代对象属性并使用find函数检查属性是否以prefix开头:

function hasPropertyStartingWith(obj, prefix) {
  return !!Object.keys(obj).find(key => {
    if (typeof obj[key] === 'object') {
      return hasPropertyStartingWith(obj[key], prefix)
    }
    if (typeof obj[key] === 'string') {
      return obj[key].startsWith(prefix)
    }
    return false
  })
}

console.log(hasPropertyStartingWith(obj, 'asd'))

【讨论】:

  • @crotoan,也许它不适用于您的实际情况,但是当其中一个属性值恰好是 nullundefined 时,此代码会生成错误,它也不会检测到数值可以以“1”或“-”之类的开头。
【解决方案4】:

您可能会使用像在 JSON 字符串上使用 RegExp 这样简单的方法,例如

var obj = {
  'child': {
    'child_key': 'asdfghhj'
  },
  'free': 'notasd',
  'with': 'asdhaheg'
};

function customStartsWith(obj, prefix) {
  return new RegExp(':"' + prefix + '[\\s\\S]*?"').test(JSON.stringify(obj));
}

console.log('obj, "asd":', customStartsWith(obj, 'asd'));
console.log('obj, "hello":', customStartsWith(obj, 'hello'));
console.log('null, "":', customStartsWith(null, ''));
console.log('undefined, "":', customStartsWith(undefined, ''));
console.log('"test", "te":', customStartsWith('test', 'te'));
console.log('12.5, 1:', customStartsWith(12.5, 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>

更新:另一个可以在 shimmed 环境中工作的递归对象遍历器。这只是一个示例,很容易定制。

var walk = returnExports;
var obj = {
  'child': {
    'child_key': 'asdfghhj'
  },
  'free': 'notasd',
  'with': 'asdhaheg'
};

function customStartsWith(obj, prefix) {
  var found = false;
  walk(obj, Object.keys, function(value) {
    if (typeof value === 'string' && value.startsWith(prefix)) {
      found = true;
      walk.BREAK;
    }
  });
  return found;
}

console.log('obj, "asd":', customStartsWith(obj, 'asd'));
console.log('obj, "hello":', customStartsWith(obj, 'hello'));
console.log('null, "":', customStartsWith(null, ''));
console.log('undefined, "":', customStartsWith(undefined, ''));
console.log('"test", "te":', customStartsWith('test', 'te'));
console.log('12.5, 1:', customStartsWith(12.5, 1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.5.9/es5-shim.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>
<script src="https://rawgithub.com/Xotic750/object-walk-x/master/lib/object-walk-x.js"></script>

【讨论】:

  • 好主意!您需要转义 prefix 中的特殊正则表达式字符,并处理 JSON 字符串中的反斜杠或 unicode 转义。
  • 是的,这肯定不是生产就绪代码,更多的是想法。 :)
猜你喜欢
  • 2020-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-31
  • 2017-10-19
  • 1970-01-01
相关资源
最近更新 更多