ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

30 seconds of code:使用递归扁平化(Flatten)与还原(Unflatten)JavaScript 嵌套对象

30 seconds of code:使用递归扁平化(Flatten)与还原(Unflatten)JavaScript 嵌套对象 教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载对象是 JavaScript 中最常用的数据结构之一但在不同场景下数据往往需要以不同的形态呈现有时你需要把深层嵌套的对象「拍平」成带路径分隔符的扁平键值对例如用于序列化、存储或表格展示有时又需要把扁平数据还原回嵌套结构。本指南基于 30 seconds of code 仓库的 JavaScript 片段 flatten-unflatten-object.md完整讲解基于递归策略实现对象扁平化与还原的算法思路、完整可运行代码、分隔符定制技巧以及关键 APIObject.keys()、Array.prototype.reduce()、Object.assign()、String.prototype.split()的底层配合方式读完即可在自己的项目中直接复制使用。为什么需要扁平化对象数据形态的转换场景数据表示形式往往各不相同对数据结构的需求也因场景而异。例如存储与序列化某些存储引擎如 Key-Value 数据库、表格视图需要扁平的键值对而嵌套对象无法直接落库配置与表单处理表单提交的数据常以user.address.city这类路径式键名传递需要先拍平再处理日志与监控将嵌套指标拍平为src.styles.main这样的键便于统一索引和检索。在这类需求中将嵌套对象转换为扁平对象或反向还原是非平凡的任务但可以借助递归策略优雅地解决。该片段被收录在 object.yaml 的 JavaScript 对象文章集合中标签为object与recursion是递归处理对象结构的典型范例。扁平化对象将每个叶节点转换为带前缀的路径节点给定一个嵌套对象扁平化的核心思路是将每一个「叶子节点」转换为一个「带完整路径前缀的扁平节点」。为便于理解先看一个实际例子——一个描述文件大小的嵌套对象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对象本身是无序的。本文所有示例为便于理解均保留了原始顺序。递归的基线条件Base Case构建递归解决方案的第一步是定义基线条件。在此场景下基线条件是值为非对象的键此时可以直接将该键值对加入结果对象。而对于值为对象的键则需要递归调用函数本身并把当前键作为前缀附加到子键上在更深层级时还必须把此前累积的所有前缀都拼接到当前键前面。按照这一算法就能逐步构建出完整的扁平化对象。JavaScript 实现reduce Object.assign 的组合在 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; }, {});三个关键设计细节这段代码虽然短小但包含了几个容易忽略的细节值得逐一拆解空对象必须当作叶子节点条件中的Object.keys(obj[k]).length 0确保空对象{}不会被继续递归而是作为普通值直接写入结果。若不处理这一点空对象会递归出「无键」的结果造成信息丢失。null必须显式排除typeof null object因此必须用obj[k] ! null把null视为叶子值否则会出现对null调用Object.keys()的运行时错误。前缀只在非空时拼接分隔符const pre prefix.length ?${prefix}${delimiter}: 保证顶层键不带多余的前缀分隔符而深层键会拼接为prefix delimiter key的形式。自定义分隔符上面的示例默认使用.作为键的分隔符但通过额外的参数即可自由定制比如改为路径风格的分隔符/// 沿用前面的 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 } */需要注意最后一个参数prefix仅供递归调用使用除非你希望每个键都带一个全局前缀例如在多个对象合并到同一命名空间时否则正常调用时应省略该参数传入任何值都会让所有键被统一加上此前缀。还原对象沿路径逐级重建嵌套结构反向操作——unflatten还原——的思路略有不同给定扁平对象中的某个键你需要以分隔符拆分路径再用拆分得到的数组逐级创建嵌套对象。实现上依赖String.prototype.split()将键按分隔符拆分为路径片段数组再用Array.prototype.reduce()沿路径逐步向下构建。reduce 的累积逻辑是若当前累积器当前层级的对象中已经存在该键对应的值则直接返回该值作为下一层累积器说明该分支已存在可继续向下延伸否则在累积器中添加对应的键值对再返回该值作为下一层累积器。此外代码还利用isNaN(Number(keys[i 1]))做了一个巧妙处理当下一个路径片段是数字时用数组[]而非对象{}作为新层级的容器从而兼容数组索引类型的路径键。最后通过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对象即可得到与原始fileSizes完全一致的嵌套结构unflattenObject(flattenedFileSizes); /* { package: 256, src: { index: 1024, styles: { main: 128, colors: 16 }, }, assets: { images: { logo: 512, background: 512 }, fonts: { serif: 64 } } } */还原算法的实现要点表达式求值而非语句赋值内层 reduce 的回调使用acc[e] || (acc[e] ...)的短路写法既完成了「已存在则复用」的判断又完成了「不存在则创建」的赋值一行代码同时承担了查重、建分支、向下传递三层职责。叶节点定位keys.length - 1 i判断当前是否为路径的最后一段若是则将原始扁平值obj[k]写入该位置否则先创建一个空容器{}或[]继续下钻。数组分支支持isNaN(Number(keys[i 1]))用于探测下一段路径是否为纯数字字符串从而用数组承接数字索引路径例如还原形如list.0.name的键时会生成数组分支。使用边界与数据丢失警告[!WARNING]扁平化与还原对象可能造成数据丢失尤其是当键本身包含分隔符时。例如原始对象中同时存在{a.b: 1, a: {b: 2}}这样两个键时扁平化后都会产生a.b后者会覆盖前者且无法再被准确还原。在处理和转换数据时务必谨慎。除此之外还有几个实践中的边界情况值得留意数组与嵌套对象的混合扁平化时数组会被当作「非空对象」递归展开typeof [] object生成的键形如items.0.name还原时虽有数组分支支持但空数组[]由于Object.keys([]).length 0会被当作叶子值直接保留行为与预期一致。分隔符冲突若数据中的键本身包含.、/等分隔符务必选用原始数据中不可能出现的字符作为分隔符否则扁平化与还原将不可逆。__proto__、constructor等特殊键还原过程会向对象写入任意路径键若扁平键包含原型链相关的特殊名称可能引发意外行为在不可信数据上使用时应格外小心。从仓库源码看该片段的定位与配套从仓库结构可以进一步确认该片段的定位与使用方式片段源文件位于 flatten-unflatten-object.mdfrontmatter 声明了tags: [object, recursion]、language: javascript被收录进 JavaScript 对象文章集合 object.yamltagMatcher: object与仓库中其他对象处理类片段构成同一知识体系仓库中与之互补的递归题材片段 flat-iterator.md 展示了另一类「拍平」——用生成器函数 yield*递归扁平化嵌套可迭代对象数组、Set 等如果你面对的是嵌套集合而非键值对象可以参考该片段redirects.yaml 中记录了该片段的历史跳转映射to: /js/s/flatten-unflatten-object说明该片段在站点路由中拥有稳定的公开地址可直接引用。如果你需要查看该片段在实际网站中的渲染效果可通过仓库的构建配置astro.config.mjs与内容加载器loader.js了解片段到页面的转换流程而Object.keys()、reduce等 API 在仓库的 ES6 辅助函数体系中也被大量复用可作为进阶学习的参照。小结对象扁平化与还原是一对典型的递归算法应用扁平化以「值为非对象」为基线条件逐层拼接路径前缀将嵌套对象压缩为路径式键值对还原则以分隔符拆分路径、逐级重建嵌套容器并在检测到数字路径片段时自动使用数组。两者都以Object.keys()Array.prototype.reduce()为骨架配合Object.assign()与String.prototype.split()完成数据搬运。核心代码均可在 flatten-unflatten-object.md 中直接取用唯一需要牢记的是当键中包含分隔符时转换可能不可逆请务必根据实际数据谨慎选择分隔符并做好数据校验。赞分享教程文档【免费下载链接】30-seconds-of-codeCoding articles to level up your development skills项目地址https://gitcode.com/gh_mirrors/30/30-seconds-of-code点击查看免费下载相关推荐Rofi 缩略图系统rofi-thumbnails完全指南XDG Thumbnailer、thumbnail:// 协议与 -preview-cmd 自定义预览Rofi 缩略图系统rofi thumbnails完全指南XDG Thumbnailer、 thumbnail:// 协议与 preview cmd 自定教程文档30 seconds of code用 JavaScript Proxy 实现不可变对象30 seconds of code用 JavaScript Proxy 实现不可变对象 对象可变性Object mutability与 const 关键教程文档30 seconds of code 深度解读JavaScript 到底是按值传递还是按引用传递30 seconds of code 深度解读JavaScript 到底是按值传递还是按引用传递 导读 在 JavaScript 开发中函数参数究竟如何教程文档创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表