将 JavaScript 对象展开或还原
数据表示经常彼此不同,数据结构的要求也不同。有时,您需要将嵌套对象转换为扁平对象,或者反之亦然。这是一个非平凡的任务,但可以使用递归策略来解决。
展开对象
给定一个对象,您可以通过将每个叶节点转换为扁平路径节点来展开它。由于这可能很难理解,这里有一个示例:
const fileSizes = {
package: 256,
src: {
index: 1024,
styles: {
main: 128,
colors: 16
},
},
assets: {
images: {
logo: 512,
background: 512
},
fonts: {
serif: 64
}
}
};
const flattenedFileSizes = {
'package': 256,
'src.index': 1024,
'src.styles.main': 128,
'src.styles.colors': 16,
'assets.images.logo': 512,
'assets.images.background': 512,
'assets.fonts.serif': 64
};
[!NOTE]
生成的键的顺序可能与原始键的顺序不匹配,因为 JavaScript 对象是无序的。本文中的所有示例都保留了原始顺序,以便更容易理解。
为了构建一个递归解决方案,我们需要定义基本情况。在这种情况下,基本情况是值不是对象的键。在这种情况下,我们可以简单地将键值对添加到结果中。
对于值是对象的键,我们需要递归调用函数,并将当前键作为前缀添加。对于更深层次的情况,我们必须将任何先前的前缀添加到当前键之前。使用这个算法,我们可以构建出展开的对象。
好的,那么我们如何在 JavaScript 中实现这个呢? 我们可以使用 Object.keys()
来获取对象的键,并使用 Array.prototype.reduce()
将每个叶节点转换为扁平路径节点。如果键的值是对象,我们使用适当的前缀递归调用函数来使用 Object.assign()
创建路径。否则,我们将适当的带前缀的键值对添加到累加器对象中。
在前面的示例中,我们使用了一个分隔符.
来分隔键,但是这可以通过使用额外的参数进行自定义。最后一个参数用于递归调用,除非您希望每个键都有一个前缀,否则应该始终省略。
const flattenObject = (obj, delimiter = '.', prefix = '') =>
Object.keys(obj).reduce((acc, k) => {
const pre = prefix.length ? `${prefix}${delimiter}` : '';
if (
typeof obj[k] === 'object' &&
obj[k] !== null &&
Object.keys(obj[k]).length > 0
)
Object.assign(acc, flattenObject(obj[k], delimiter, pre + k));
else acc[pre + k] = obj[k];
return acc;
}, {});
// 假设之前的对象 `fileSizes`
flattenObject(fileSizes, '/');
/* {
'package': 256,
'src/index': 1024,
'src/styles/main': 128,
'src/styles/colors': 16,
'assets/images/logo': 512,
'assets/images/background': 512,
'assets/fonts/serif': 64
} */
反转对象的扁平化
反转这个过程有点不同。给定扁平化对象中的一个键,您需要在分隔符处拆分路径,并使用生成的数组来创建嵌套对象。
为了做到这一点,我们可以使用String.prototype.split()
来在分隔符处拆分键,并使用Array.prototype.reduce()
来根据键添加对象。如果当前累加器已经包含了特定键的值,我们将其值作为下一个累加器返回。否则,我们将适当的键值对添加到累加器对象中,并将值作为累加器返回。最后,我们可以使用Object.keys()
和Array.prototype.reduce()
将此应用于扁平化对象中的每个键。
const unflattenObject = (obj, delimiter = '.') =>
Object.keys(obj).reduce((res, k) => {
k.split(delimiter).reduce(
(acc, e, i, keys) =>
acc[e] ||
(acc[e] = isNaN(Number(keys[i + 1]))
? keys.length - 1 === i
? obj[k]
: {}
: []),
res
);
return res;
}, {});
// 假设之前的对象 `flattenedFileSizes`
unflattenObject(flattenedFileSizes);
/* {
package: 256,
src: {
index: 1024,
styles: {
main: 128,
colors: 16
},
},
assets: {
images: {
logo: 512,
background: 512
},
fonts: {
serif: 64
}
}
} */
[!WARNING]
对象的扁平化和反扁平化可能会导致数据丢失,特别是如果键包含分隔符。在处理和转换数据时要始终小心。