ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Effect Formatter.formatJson 详解:只省略循环引用、保留共享引用,并移除 Inspectable.stringifyCircular

Effect Formatter.formatJson 详解:只省略循环引用、保留共享引用,并移除 Inspectable.stringifyCircular Effect Formatter.formatJson 详解只省略循环引用、保留共享引用并移除 Inspectable.stringifyCircular【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect本篇技术指南围绕 Effect 仓库中的一条 patch 级变更记录展开移除Inspectable.stringifyCircular辅助函数并修复Formatter.formatJson的引用处理语义使其在序列化时保留共享diamond对象引用、只省略真正的循环引用。读完后你将理解 Effect 中format与formatJson两个值格式化函数对循环引用的判定原理“当前祖先路径”语义并能结合源码与测试用例验证、正确使用这两个 API。1. 变更背景这条 changeset 描述了什么本次变更由 changeset 文件 记录内容非常凝练原文共两点--- effect: patch --- Remove Inspectable.stringifyCircular and fix Formatter.formatJson so shared object references are preserved while only circular references are omitted.即移除Inspectable.stringifyCircular从Inspectable模块中删掉这个用于处理循环引用的字符串化辅助函数修复Formatter.formatJson让“共享对象引用”同一对象被多个属性引用但不构成环在 JSON 输出中被完整保留只有“循环引用”对象沿着自身祖先链再次出现才会被省略而不是像朴素实现那样把两者一并丢弃。changeset 文件位于.changeset/pre/目录下。从 .changeset/pre.json 可以看到仓库当前处于 changesets 的 pre-release 模式mode: pre、tag: rc因此该记录会随 rc 版本流程一并应用。变更级别为patch说明它不改变公开 API 的兼容性只修正行为并移除一个旧辅助函数。仓库 packages/effect/CHANGELOG.md 中已收录了这条变更的发布记录“RemoveInspectable.stringifyCircularand fixFormatter.formatJsonso shared object references are preserved while only circular references are omitted”。2. Inspectable.stringifyCircular 的移除Inspectable 模块的现状在 packages/effect/src/Inspectable.ts 中stringifyCircular已不存在。通过对整个仓库搜索stringifyCircular可以确认packages/目录下仅剩packages/effect/CHANGELOG.md的历史记录中提及该名称源码中已无任何定义或调用——这与 changeset 的“Remove”描述一致。当前Inspectable模块对外暴露的 API 面为API作用位置NodeInspectSymbolSymbol.for(nodejs.util.inspect.custom)供 Node.jsutil.inspect()/REPL 自定义对象展示Inspectable.ts#L49Inspectable接口要求toString()、toJSON()、[NodeInspectSymbol]()三个成员Inspectable.ts#L126-L130toJson先redact再调用零参toJSON()失败时返回[toJSON threw]Inspectable.ts#L153-L169toStringUnknown对象走formatJson、其他值走format兜底String(u)Inspectable.ts#L187-L196BaseProto/Class为自定义值提供统一的检查inspect行为基座Inspectable.ts#L234-L244、L291对使用者而言移除stringifyCircular的含义是自定义数据类型的字符串化/JSON 化应统一走format/formatJson而不再依赖 Inspectable 模块内部的循环引用字符串化工具。模块 JSDoc 也说明了这一设计意图Inspectable负责“控制值在日志和调试输出中的呈现”而循环引用的处理职责收敛到了Formatter模块见下节。自定义类型推荐的写法仍是继承Inspectable.Class并只实现toJSON()toString()与 Node inspect 行为由基类通过format(this.toJSON())提供Inspectable.ts#L291-L331。3. formatJson 的修复核心共享引用 vs 循环引用3.1 为什么需要区分这两类引用直接对含环的对象调用JSON.stringify会抛出TypeError: Converting circular structure to JSON。一个常见的“安全包装”方案是用WeakSet记录所有“已经见过”的对象再次遇到就跳过——但这会误伤共享引用const shared { a: 1 } // 期望输出两个完整副本 // {left:{a:1},right:{a:1}} JSON.stringify({ left: shared, right: shared })如果只按“是否出现过”判重right会被错误地省略。本次修复确立的语义是以“当前序列化路径”祖先栈为判定基准——只有当一个对象再次出现在当前路径的祖先上时才是循环引用才返回undefined让它从输出中消失而路径之外重复出现的共享对象则正常完整序列化。3.2 源码实现replacer 内的祖先栈修复后的formatJson实现位于 packages/effect/src/Formatter.ts#L302-L330。核心逻辑export function formatJson(input: unknown, options?: { readonly space?: number | string | undefined }): string { const ancestors: Arrayobject [] return JSON.stringify( input, function(this: object, key: string, value: unknown) { const original Object.getOwnPropertyDescriptor(this, key)?.value const redacted Predicate.hasProperty(original, symbolRedactable) ? redact(original) : redact(value) if (typeof redacted bigint) { return format(redacted) } if (typeof redacted ! object || redacted null) { return redacted } while (ancestors.length 0 ancestors[ancestors.length - 1] ! this) { ancestors.pop() } if (ancestors.includes(redacted)) { return undefined // circular reference } ancestors.push(redacted) return redacted }, options?.space ) ?? null }几个关键点祖先栈的进栈/出栈即“路径”语义ancestors数组模拟了JSON.stringify的回溯过程。每次处理完一个对象后while循环把栈顶回退到当前thisFormatter.ts#L319-L321因此任意时刻栈中元素恰好是“当前对象到根的路径”。共享对象在left分支处理完出栈后处理right时ancestors.includes(redacted)为false于是被完整序列化第二次而真正的环obj.self obj会让obj仍留在当前祖先栈中从而返回undefined被JSON.stringify省略Formatter.ts#L322-L325。Redactable 脱敏通过Object.getOwnPropertyDescriptor(this, key)?.value先取“原始自有属性值”再判断symbolRedactableFormatter.ts#L309-L312避免 replacer 拿到的已经是toJSON()之后的结果保证Redacted值在任何位置都会被脱敏为[REDACTED]。BigInt 支持JSON 原生不支持bigint这里用format(redacted)产出形如123n的字符串Formatter.ts#L313-L315。根级返回值兜底当根输入是undefined、symbol或函数时JSON.stringify返回undefined末尾的?? null将其统一为null。这是模块 JSDoc 中明确写出的“Gotchas”Formatter.ts#L264-L268。space选项透传给JSON.stringify控制缩进缺省为0紧凑输出。formatJson的 JSDocFormatter.ts#L246-L301给出的示例也与上述语义一致import { Formatter } from effect Formatter.formatJson({ name: Alice, age: 30 }) // {\name\:\Alice\,\age\:30} const obj: any { name: test } obj.self obj Formatter.formatJson(obj) // {\name\:\test\} // self 被省略不抛错 const output Formatter.formatJson({ name: Alice, age: 30 }, { space: 2 }) // {\n \name\: \Alice\,\n \age\: 30\n}3.3 对照format 的人读字符串语义formatFormatter.ts#L108-L195产出非JSON 的可读字符串供日志、诊断与错误消息使用。它用WeakSet记录当前递归路径进入前add、返回前delete同样是路径语义命中时输出[Circular]占位符而不抛错Formatter.ts#L152-L153、L197。因此共享引用在format中同样会出现两次与formatJson的修复语义保持一致const obj: any { name: loop } obj.self obj Formatter.format(obj) // {\name\:\loop\,\self\:[Circular]}两者选型建议需要可解析的 JSON如结构化日志、错误上报用formatJson需要人读友好Map、Set、BigInt、RegExp、自定义toString、Error.cause等JSON.stringify无法表达的形态用format。formatJson在仓库中被Logger相关代码及 Effect 内部实现引用见 packages/effect/src/Logger.ts 与 packages/effect/src/internal/effect.ts所以这次引用语义的修复会直接作用于运行期日志与诊断输出的正确性。4. 测试用例语义修复的可验证依据packages/effect/test/Formatter.test.ts 中的断言完整覆盖了两种引用形态是验证本次行为的最直接证据。format侧Formatter.test.ts#L107-L143it(circular object, () { const obj: any { a: 1 } obj.b obj strictEqual(format(obj), {a:1,b:[Circular]}) }) it(preserves repeated non-circular references, () { const shared { value: 1 } strictEqual(format({ first: shared, second: shared }), {first:{value:1},second:{value:1}}) })同一文件还覆盖了循环数组[1,[Circular]]、循环Map/Set内容等边界形态L107-L125。formatJson侧Formatter.test.ts#L325-L344it(should omit circular references, () { // obj.a 1; obj.self obj strictEqual(formatJson(obj), {a:1}) }) it(preserves repeated non-circular references, () { const shared { a: 1 } strictEqual(formatJson({ left: shared, right: shared }), {left:{a:1},right:{a:1}}) }){left:{a:1},right:{a:1}}这一断言正是 changeset 所述“shared object references are preserved”的直接验证共享引用完整保留self这类真环才从输出中消失。此外测试还确认了formatJson(undefined) null的根级兜底、BigInt的123n字符串化以及Redacted值在对象、嵌套对象与数组中的脱敏输出L325-L355。5. 适用前提与使用建议版本前提format标注since 2.0.0formatJson标注since 4.0.0见 Formatter.ts#L106、L300且当前仓库处于 rc 预发布模式stringifyCircular的移除与formatJson的语义修复将在该 pre-release 周期内随patch版本生效如果你依赖的是更早期的已发布版本行为可能不同以对应版本的 CHANGELOG 为准。选型日志/诊断/错误消息用format可读、容错[Circular]占位不抛错需要下游可JSON.parse的输出用formatJson合法 JSON环被静默省略共享引用保留。边界行为formatJson对 JSON 原生不支持的值仍遵循JSON.stringify的标准行为如函数、undefined在嵌套位置仅根级特殊输入会被统一为nullformat则对检查过程中的异常渲染为[inspection threw]等诊断占位符而非抛出。自定义类型如需让自己的值在日志与util.inspect中呈现良好继承Inspectable.Class实现toJSON()即可序列化与循环引用处理交给Formatter模块不要再自行实现字符串化的判环逻辑。【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表