过滤 obj[array[@]] 为 nil 或空的数组

Posted

技术标签:

【中文标题】过滤 obj[array[@]] 为 nil 或空的数组【英文标题】:Filter an array where obj[array[@]] is nil or empty 【发布时间】:2021-09-28 10:31:51 【问题描述】:

再次练习 Ramda。

所以情况是我有一个obj:

const originalObj = 
  foo: "bar",
  std: "min",
  baz: "",
  key1: undefined,
  key2: "exit",
  key3: "val3",
  key4: "",
;

我有一个事先知道的数组:

const toCheckArray = ["baz", "key1", "key2", "key3", "key4", "key5"];

对于数组中的每个元素,我需要检查obj中是否存在这样的元素(作为key),以及对应的值是否为nil/empty。如果这样的键存在,并且值是非零/空的,那么我会像这样进行 HTTP 调用来更新值:

const findKey2AndUpdateObj = async (originalObj) => 
  const originalKey2 = originalObj.key2;
  const key2 = await remoteHttpCall(originalKey2);
  return  ...originalObj, key2: key2 ;
;

对于数组中的所有元素,远程 HTTP 调用将完全相同,当然除了有效负载。

我的做法是先过滤列表,执行以下步骤:

    const hasArray = filter(has(__, originalObj), toCheckArray);这个我相信会检查目标obj中是否存在作为prop的元素; 我正在尝试将complement(anyPass([isNil, isEmpty])) 应用于 obj 的所有值,然后以某种方式过滤数组中的相应键; 迭代数组?进行 API 调用,然后更新 obj。

我想我的想法并不是最好的方法。很想听听你的想法! 记住 API 调用也很棒!


或者我应该将第 1 步和第 2 步颠倒过来?从 obj 中过滤掉所有的 nil/empty ,然后进行has 检查。


我最终这样做了: filter(has(__, reject(anyPass([isEmpty, isNil]))(obj)), __)(arr)。但肯定有更好的方法。

干杯!

【问题讨论】:

(不知道 Ramda 是什么......)for (toCheck of toCheckArray) if (originalObj[toCheck]) doStuff() 有什么问题 嗨@Tibrogargan!不,这绝对没有错!而且我猜你会想象我知道用js解决这个问题的普通方法。我正在练习 FP,这个问题用 FP 标记。 因为你在“做 FP”而让事情变得更复杂并不是一个理由。但这是一种 FP 方式,用更多的击键次数和 [可以说] 更少的可读性来做同样的事情:toCheckArray.forEach( toCheck => if (originalObject[toCheck]) doStuff(originalObject[toCheck]) ) 你可以做类似intersection(keys(reject(either(isNil, isEmpty))(obj)))(arr)的事情,但它非常相似 for 绝对有问题。每次写这 3 个字母时,几乎可以肯定地将数据结构逻辑与业务逻辑耦合在一起,从而使重构变得更加困难。请考虑我的实现,其中传递了逻辑,使其更易于共享/重用。 【参考方案1】:

使用管道,您可以使其功能更强大一点,因为您可以将对象传递到管道并输出键,类似于

pipe(reject(either(isNil, isEmpty)),keys,intersection(arr))(obj)

然后您可以将其通过管道传输到 api 调用中(使用 pipeWith

【讨论】:

【参考方案2】:

我会使用 R.pick 获取仅包含请求键的部分对象,然后使用 R.reject 进一步过滤它们。由于结果是一个对象,您可以使用 R.toPairs,并迭代对进行 api 调用,并用新值重构对象。

const  curry, pipe, pick, reject, anyPass, isEmpty, isNil  = R;

const fn = curry((arr, obj) => pipe(
  pick(arr), 
  reject(anyPass([isEmpty, isNil])),
)(obj));

const toCheckArray = ["baz", "key1", "key2", "key3", "key4", "key5"];
const originalObj = "foo":"bar","std":"min","baz":"","key2":"exit","key3":"val3","key4":"";

const result = fn(toCheckArray, originalObj);

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

【讨论】:

【参考方案3】:

函数式编程不一定要使用 Ramda, 有时(例如异步任务),它可能对可读性和声明性代码没有帮助。我会在这里使用简单的递归。

const asyncUpdater = async (keys, updater, data) => 
  // base case
  if(!keys.length)  return data ;
  
  const [head, ...tail] = keys;
  const next = (d) => asyncUpdater(tail, updater, d);
  
  const value = data[head];
  
  // continue
  if(!value)  return next(data); 
  
  return next( 
    ...data, 
    [head]: await updater(head, value)
  )
;

// ===
const keys = ["baz", "key1", "key2", "key3", "key4", "key5"];

const data = 
  foo: "bar",
  std: "min",
  baz: "",
  key1: undefined,
  key2: "exit",
  key3: "val3",
  key4: "",
;

function fakeHttp(key, value) 

  return Promise.resolve(`$value__REMOTELY_UPDATED__`);


asyncUpdater(keys, fakeHttp, data).then(console.log);

【讨论】:

【参考方案4】:

我希望我正确理解了您的要求。干净、自我记录,并且功能与原生 javascript 一样。

const resolveProperties = curry((fn, picklist, input) =>
  Promise.resolve(input)
    .then(pickAll(picklist))
    .then(reject(anyPass([isNil, isEmpty])))
    .then(map(fn))
    .then(Promise.props)
);

使用和测试用例:

import  pickAll, reject, isNil, map, anyPass, isEmpty, curry  from "ramda";
import  Promise  from "bluebird";

const mockHttp = (input) =>
  new Promise((res) => setTimeout(res(`$input resolved`), 100));

const resolveProperties = curry((fn, picklist, input) =>
  Promise.resolve(input)
    .then(pickAll(picklist))
    .then(reject(anyPass([isNil, isEmpty])))
    .then(map(fn))
    .then(Promise.props)
);

test("resolveProperties", () => 
  const list = ["baz", "key1", "key2", "key3", "key4", "key5"];

  const input = 
    foo: "bar",
    std: "min",
    baz: "",
    key1: undefined,
    key2: "exit",
    key3: "val3",
    key4: "",
  ;

  return resolveProperties(mockHttp, list, input).then((result) =>
    expect(result).toEqual(
      key2: "exit resolved",
      key3: "val3 resolved",
    )
  );
);

【讨论】:

Promise.props 是从 bluebird 导入的

以上是关于过滤 obj[array[@]] 为 nil 或空的数组的主要内容,如果未能解决你的问题,请参考以下文章

nil/Nil/NULL/NSNull

js怎么判断空数组

ruby puts, print, p方法比较

仅当过滤器值不是空字符串、空格或空值时过滤 MySQL 查询最佳实践

防止 Obj-C 代码将 `nil` 传递给具有非可选参数的 Swift 方法

nil/Nil/NULL/NSNull区别