ES6 / lodash -用startsWith检查嵌套对象值

ES6 / lodash - check nested Object values with startsWith

本文关键字:检查 嵌套 对象 startsWith lodash ES6      更新时间:2023-09-26

是否有任何函数或任何快速的方法来检查某个值是否在我们的对象开始与例如asd

的例子:

let obj = { 
   'child' : {
      'child_key': 'asdfghhj'
    },
    'free': 'notasd',
    'with': 'asdhaheg'
  }
// check here if our obj has value that startsWith('asd')

如果你真的不关心哪个节点/值匹配,使用@trincot的解决方案。它很简单,写得很好,很有效地解决了你的问题。

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


我真的怀疑你是否需要这样做,但是如果你的对象非常大,你会想要一个早期退出行为 -这意味着一旦找到匹配,通过输入数据的迭代将停止,true/false结果将立即返回。@trincot的解决方案提供了早期退出,但使用map, filterreduce的解决方案没有这种行为。

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'

下面是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,因为这个值显然不是从给定的搜索字符串开始的;

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

  3. 以上皆非。在这种情况下,我们将其强制转换为string(通过应用String函数),并对其应用startsWith

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

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

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

非递归选择

为了回答关于潜在堆栈限制的评论,这里有一个替代的非递归函数,它在变量中维护一个"堆栈":

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

您可以使用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'))

您可以使用一些简单的东西,如在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>

更新:另一个将在模糊环境中工作的递归对象行走器。这只是一个例子,它很容易定制。

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>