ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Effect 中 `Schema.fromJsonString` 的 JSON Schema 标识符保留:修复客户端代码生成中的载荷类型命名丢失

2026/9/15 19:02:45 拓冰建站 浏览量
Effect 中 `Schema.fromJsonString` 的 JSON Schema 标识符保留:修复客户端代码生成中的载荷类型命名丢失 Effect 中Schema.fromJsonString的 JSON Schema 标识符保留修复客户端代码生成中的载荷类型命名丢失【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effectSchema.fromJsonString是 Effect Schema 中用于将JSON 字符串与结构化 Schema相互转换的核心构造广泛用于 RPC、HttpApi、持久化与消息传输等需要把结构化载荷塞进字符串字段的场景。本篇基于 effect 仓库中 .changeset/pre/fix-from-json-string-identifier.md 所记录的 patch 修复深入讲解该函数在生成 JSON Schema 时如何保留内容 Schema 的用户自定义标识符identifier、为字符串包装类型派生独立名称以及这一行为对 OpenAPI/客户端代码生成的关键影响。读完你将理解 fromJsonString 的底层实现、标识符注解的传播规则以及如何显式控制编码侧wire类型的命名。一次 patch 修复了什么该 changeset 记录了 effect 包的一次 patch 级修复原文如下Preserve content schema identifiers when emitting JSON Schema forSchema.fromJsonString. This keeps user-defined identifiers attached to the decoded JSON payload while giving the generated JSON string wrapper its own derived name, avoiding client codegen outputs where the payload type is renamed behind the transport wrapper.翻译过来本次修复解决的是两个层面的问题保留内容标识符在通过Schema.fromJsonString生成 JSON Schema 时内层内容 Schemacontent schema上用户自定义的identifier注解不再丢失而是始终附着在解码后的 JSON 载荷类型上派生包装名称外层JSON 字符串包装类型拥有自己的派生名称二者互不冲突。这样做的直接收益是基于 OpenAPI / JSON Schema 的客户端代码生成client codegen不会再因为传输层包装而把真正的载荷类型改名——比如服务端定义的MyEvent不会在客户端变成MyEventEncoded之外的、难以识别的名字。Schema.fromJsonString是什么在深入修复细节前先看这个函数本身。它的定义位于 packages/effect/src/Schema.tsexport function fromJsonStringS extends Constraint( schema: S, options?: { readonly reviver?: Parameterstypeof JSON.parse[1] | undefined readonly replacer?: SchemaGetter.JsonReplacer | undefined readonly space?: Parameterstypeof JSON.stringify[2] | undefined } ): fromJsonStringS { return JsonString.pipe(decodeTo(schema, SchemaTransformation.fromJsonString(options))) }其类型层面表示为fromJsonStringS extends decodeToS, StringSchema.ts即解码时把 JSON 字符串解析为内容类型 S编码时把内容类型 S 序列化为字符串。函数内部由两个部分组成一个预置的字符串包装 SchemaJsonString携带contentMediaType: application/json与expected: a string that will be decoded as JSON注解Schema.ts一个SchemaTransformation.fromJsonString(options)变换负责真正的解析与序列化SchemaTransformation.tsexport function fromJsonString(options?: { readonly reviver?: Parameterstypeof JSON.parse[1] | undefined readonly replacer?: SchemaGetter.JsonReplacer | undefined readonly space?: Parameterstypeof JSON.stringify[2] | undefined }): Transformationunknown, string { return new Transformation( SchemaGetter.parseJson(options ?? {}), SchemaGetter.stringifyJson(options) ) }三个可选参数与原生 JSON API 一一对应参数阶段作用默认值reviver解码传给JSON.parse逐值转换解析结果无replacer编码传给JSON.stringify过滤/转换序列化字段无space编码传给JSON.stringify控制缩进格式无典型的用法如官方文档示例Schema.tsimport { Schema } from effect const schema Schema.Struct({ a: Schema.Number }) const schemaFromJsonString Schema.fromJsonString(schema, { space: 2 }) Schema.encodeSync(schemaFromJsonString)({ a: 1 }) // {\n \a\: 1\n}解码失败时非法 JSON会以InvalidValue错误失败编码时若JSON.stringify无法序列化该值同样以InvalidValue失败。identifier 注解Schema 的类型名Effect Schema 的identifier注解用于给一个 Schema 一个稳定的、人类可读的名称它是 JSON Schema 生成、代码生成、错误信息展示等场景的命名基础。例如const MyEvent Schema.Struct({ value: Schema.String }).annotate({ identifier: MyEvent })当这样一个带标识符的 Schema 被包裹进fromJsonString后问题随之而来外层字符串包装和内容 Schema 是两个不同的 Schema二者都需要名字。如果实现不当内层的MyEvent就会在 JSON Schema 文档中被内层包装类型的名字覆盖客户端代码生成出来的载荷类型名就会被静默改名。修复后的 JSON Schema 生成行为本次修复的核心是内容标识符保留包装类型获得派生名。仓库中的测试用例packages/effect/test/schema/toJsonSchemaDocument.test.ts完整刻画了这一行为是理解该修复最直接的证据。顶层使用无内容标识符时的输出当fromJsonString被直接用作顶层 Schema 且内容 Schema 没有 identifier 时生成的 JSON Schema 就是一个携带contentMediaType的字符串Schema.fromJsonString(Schema.FiniteFromString) // 生成的 JSON Schema: { type: string, contentMediaType: application/json }此时字符串包装 Schema 即整个文档无$defs定义。保留内容标识符包装类型派生Encoded名称当内容 Schema 带有 identifier 时修复后的行为是const MyEvent Schema.Struct({ value: Schema.String }).annotate({ identifier: MyEvent }) Schema.fromJsonString(MyEvent) // 生成的 JSON Schema: { schema: { $ref: #/$defs/MyEventEncoded }, definitions: { MyEventEncoded: { type: string, contentMediaType: application/json } } }关键点有两个内容标识符MyEvent得以保留解码后的 JSON 载荷类型在客户端 codegen 中仍然是MyEvent不会再被传输层包装改名包装类型获得派生名MyEventEncoded即内容标识符 Encoded后缀用来命名 JSON 字符串包装本身。同一个测试文件中Schema.Class的用例toJsonSchemaDocument.test.ts也印证了这一派生规则类A生成#/$defs/AEncoded。这一设计正是 changeset 中所说的 giving the generated JSON string wrapper its own derived name——载荷类型与传输包装各得其所二者互不侵占命名空间。显式覆盖编码侧标识符如果默认的派生名Encoded后缀不符合项目约定可以通过flipannotate显式指定编码侧wire 侧的名称const MyEvent Schema.Struct({ value: Schema.String }).annotate({ identifier: MyEvent }) const MyWireEvent Schema.flip( Schema.flip(Schema.fromJsonString(MyEvent)).annotate({ identifier: MyWireEvent }) ) // 生成的 JSON Schema: { schema: { $ref: #/$defs/MyWireEvent }, definitions: { MyWireEvent: { type: string, contentMediaType: application/json } } }借助两次flip将命名注解落到字符串包装那一侧即可把 wire 类型命名为MyWireEvent而内容类型依然保持MyEvent。为什么这对生产项目至关重要fromJsonString并非孤立工具它在仓库中已被广泛用于真实传输场景从源码检索可以看到它在以下模块中被引用例如 HttpApiSchema.ts、RpcSchema 相关的 SchemaTransformation 引用、KeyValueStore.ts、DurableDeferred.ts 等RPC 序列化请求/响应被编码成字符串后经传输层收发HttpApi / OpenAPI接口描述文档中字符串载荷字段需要contentMediaType: application/json同时$defs中的类型名直接决定 OpenAPI 生成器输出到各语言的类型名持久化与工作流KeyValueStore、DurableDeferred等场景把结构化数据以 JSON 字符串形式落库。在这些场景下一旦 JSON Schema 中的类型名不稳定或丢失客户端 codegenOpenAPI 生成器、GraphQL 风格工具链等输出的类型就会与服务端定义脱节——MyEvent变成一串派生命名代码评审、跨团队接口对齐都会受影响。本次修复通过内容标识符保留 包装类型派生名的组合策略从根源上消除了这类载荷类型被传输包装改名的隐患同时保留了显式覆盖的逃生通道。小结Schema.fromJsonString由字符串包装 SchemacontentMediaType: application/jsonSchemaTransformation.fromJsonString变换组合而成支持reviver/replacer/space三个原生 JSON 参数修复后生成 JSON Schema 时内容 Schema 的用户 identifier 完整保留在解码载荷侧字符串包装类型自动获得identifierEncoded的派生名需要自定义 wire 命名时可用flipannotate({ identifier })显式指定该行为由 toJsonSchemaDocument.test.ts 中的三个用例覆盖并直接影响 RPC、HttpApi/OpenAPI、持久化等生产传输场景的客户端代码生成质量。如果你正在维护基于 Effect 的 HTTP API 或 RPC 服务并依赖 OpenAPI/JSON Schema 生成客户端建议升级后检查$defs中的类型命名确认载荷类型名与内容 Schema 的 identifier 保持一致。【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考