保留有用的功能的一个好处是,您通常可以非常简单地解决您的新需求。使用我多年来编写的一些库函数,我能够编写这个版本:
const removeEmpties = (input) =>
pathEntries (input)
.filter (([k, v]) => v !== '')
.reduce ((a, [k, v]) => assocPath (k, v, a), {})
这使用了我周围的两个函数,pathEntries 和 assocPath,我将在下面给出它们的实现。给定您提供的输入时,它会返回以下内容:
{
name: "fff",
onlineConsultation: false,
primaryLocation: {
locationName: "ggg"
},
education: [
{
nameOfInstitution: "ffff",
description: "fff"
}
]
}
这将删除空字符串、没有值的数组(在删除空字符串之后)和没有非空值的对象。
我们首先调用pathEntries(我在这里的其他答案中使用过它,包括a fairly recent one。)这会收集输入对象中所有叶节点的路径以及这些叶节点的值。路径存储为字符串数组(对于对象)或数字(对于数组)。它们嵌入到带有值的数组中。所以在这一步之后我们会得到类似的东西
[
[["name"], "fff"],
[["onlineConsultation"], false],
[["image"], ""],
[["primaryLocation", "locationName"], "ggg"],
[["primaryLocation", "street"], ""],
[["categories", 0], ""],
[["concernsTreated", 0], ""],
[["education", 0, "nameOfInstitution"], "ffff"],
[["education", 0, "description"],"fff"],
[["experience", 0, "from"], ""],
[["experience", 0, "current"], ""]
]
这应该类似于 Object.entries 的对象结果,除了键不是属性名称而是整个路径。
接下来我们过滤掉任何一个空字符串值,产生:
[
[["name"], "fff"],
[["onlineConsultation"], false],
[["primaryLocation", "locationName"], "ggg"],
[["education", 0, "nameOfInstitution"], "ffff"],
[["education", 0, "description"],"fff"],
]
然后通过在这个列表和一个空对象上减少对assocPath(另一个我用过很多次的函数,包括在a very interesting question中)的调用,我们将一个完整的对象与正确的叶节点结合起来路径,我们得到了我们正在寻找的答案。 assocPath 是另一个函数assoc 的扩展,它将属性名称与对象中的值不变地关联起来。虽然不是这么简单,但由于要处理数组和对象,您可以认为 assoc 就像 (name, val, obj) => ({...obj, [name]: val}) assocPath 对对象路径而不是属性名称做了类似的事情。
关键是我只为此编写了一个新函数,并且使用了我周围的东西。
通常我更愿意为此编写一个递归函数,并且我did so recently 解决了类似的问题。但这并不容易扩展到这个问题,如果我理解正确,我们想在数组中排除一个空字符串,然后,如果该数组本身现在是空的,也排除它。这种技术使这很简单。在下面的实现中,我们将看到pathEntries 依赖于递归函数,而assocPath 本身就是递归的,所以我想递归还在进行中!
我还应该注意assocPath 和pathEntries 中使用的path 函数的灵感来自Ramda(免责声明:我是它的作者之一。)我在@ 987654325@,只有在它工作之后,我才将它移植到 vanilla JS,使用我为之前的问题创建的依赖项版本。所以虽然下面的sn-p里面有很多函数,但是写起来还是挺快的。
const path = (ps = []) => (obj = {}) =>
ps .reduce ((o, p) => (o || {}) [p], obj)
const assoc = (prop, val, obj) =>
Number .isInteger (prop) && Array .isArray (obj)
? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
: {...obj, [prop]: val}
const assocPath = ([p = undefined, ...ps], val, obj) =>
p == undefined
? obj
: ps.length == 0
? assoc(p, val, obj)
: assoc(p, assocPath(ps, val, obj[p] || (obj[p] = Number .isInteger (ps[0]) ? [] : {})), obj)
const getPaths = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, v]) => getPaths (v) .map (p => [Array.isArray(obj) ? Number(k) : k, ... p])
)
: [[]]
const pathEntries = (obj) =>
getPaths (obj) .map (p => [p, path (p) (obj)])
const removeEmpties = (input) =>
pathEntries (input)
.filter (([k, v]) => v !== '')
.reduce ((a, [k, v]) => assocPath (k, v, a), {})
const input = {name: "fff", onlineConsultation: false, image: "", primaryLocation: {locationName: "ggg", street:""}, billingAndInsurance: [], categories: [""], concernsTreated: [""], education: [{nameOfInstitution: "ffff", description: "fff"}], experience: [{from: "", current:""}]}
console .log(removeEmpties (input))
在某些时候,我可能会选择更进一步。我看到一个 hydrate 函数要被拉出:
const hydrate = (entries) =>
entries .reduce ((a, [k, v]) => assocPath2(k, v, a), {})
const removeEmpties = (input) =>
hydrate (pathEntries (input) .filter (([k, v]) => v !== ''))
而且我还可以看到这样写的更像 Ramda 风格:
const hydrate = reduce ((a, [k, v]) => assocPath(k, v, a), {})
const removeEmpties = pipe (pathEntries, filter(valueNotEmpty), hydrate)
使用适当版本的valuesNotEmpty。
但这一切都是为了另一天。