vscode RFC: (工程) 引入可反射的反序列化助手

0vvn1miw  于 2个月前  发布在  Vscode
关注(0)|答案(6)|浏览(32)

当前状态
我们在几个地方进行JSON验证/反序列化。
验证可能会失败,需要插入错误消息、默认值,通常还会提供一个JSON模式。
以下是我们目前如何实现的一些示例:

  • 语言配置
  • 验证:

vscode/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts
第167行 0f4df3d
| | if(!Array.isArray(source)){ |

  • JSON模式:

vscode/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts
第545行 0f4df3d
| | brackets: { |

  • 默认值?
  • 编辑器设置
  • TypeScript类型定义:

vscode/src/vs/editor/common/config/editorOptions.ts
第2535行 0f4df3d
| | side?: 'right'|'left'; |

  • 验证:

vscode/src/vs/editor/common/config/editorOptions.ts
第2637行 0f4df3d
| | side: stringSet<'right'|'left'>(input.side,this.defaultValue.side,['right','left']), |

  • 默认值:

vscode/src/vs/editor/common/config/editorOptions.ts
第2570行 0f4df3d
| | side: 'right', |

  • JSON模式:

vscode/src/vs/editor/common/config/editorOptions.ts
第2595行 0f4df3d
| | 'editor.minimap.side': { |

  • 在命令中处理args的处理过程

问题

当前进行JSON反序列化/验证的方式非常冗长且不一致。
没有保证JSON模式与验证相匹配(尽管有时这是有意为之的)。

建议解决方案

我建议探索引入一个通用的、描述性的反序列化辅助程序,允许在单个地方指定TypeScript类型、验证逻辑、默认值和人类可读的描述。可以从那里派生出JSON模式。
结果应该是一个内部辅助程序(而不是外部库),类似于io-ts或我自己的库hediet/semantic-json(示例)。
editor.minimap.side的情况下,代码应该如下所示:

const editorMinimap = objectDeserializer({
    properties: {
        ...
        side: property(enumDeserializer(['left', 'right']), {
            defaultValue: 'left',
            description: nls.localize('minimap.side', "Controls the side where to render the minimap.")
        })
        ...
    }
});

const editorSettings = objectDeserializer({
    properties: {
        ...
        minimap: editorMinimap
        ...
    }
});

assert.deepStrictEqual(flatten(objectDeserializer({ properties: { editor: editorSettings } })).getJSONSchema(), {
    ...
    "editor.minimap.side": {
        type: 'string',
        enum: ['left', 'right'],
        default: 'left',
        description: "Controls the side where to render the minimap."
        ...
    }
    ...
});

对此感兴趣的人可能是:
@alexdima@connor4312@aeschli
如果对此有兴趣进一步探讨,我很乐意进一步研究这个想法。

vuktfyat

vuktfyat1#

我也在使用方法2为LSIF生成验证器。请参阅https://github.com/microsoft/lsif-node/blob/fd6a872b17da1fc05029271445c76aae70e35fc6/protocol/src/protocol.ts#L171-L171

slhcrj9b

slhcrj9b2#

总的来说,我认为我们应该探索代码生成。所有的设置都可以用一个或多个JSON文件(可能使用JSON schema或JSON schema++)来描述,然后可以生成类型定义(带有漂亮的JSDoc注解)或高效的类型验证代码。但这需要在团队中讨论并达成一致。
从编辑器选项的Angular 来看,我非常希望看到一个明确的提案,解决以下问题:

  • TS类型必须在monaco.d.ts中通过JSDoc注解提供,因为这是编辑器嵌入者最终编译的代码。我们有时也会从我们的代码库示例化编辑器(例如REPL编辑器),我们需要在那里进行类型验证。
  • JSDoc注解必须包含默认值,以便通知开发人员有关选项的默认值。
  • 编辑器选项还必须通过JSON schema提供给设置服务/注册表。这里也必须给出一个默认值。
  • 从配置服务传递给编辑器的设置必须经过验证
  • 从API用户(在嵌入器情况下)传递给编辑器的选项必须经过验证
  • 在多次调用updateOptions的情况下,选项必须高效地计算
  • 调用updateOptions应最终创建一个可查询的选项更改事件,该事件针对已更改的选项。
  • 编辑器选项必须快速读取,因为视图/视图模型/布局等内部的许多代码都需要读取选项。
svmlkihl

svmlkihl3#

如果我们采用代码生成的方法来解决 .d.ts 问题,我会投票支持简单地使用 JSON schema。实际上,在构建服务 API 之前,我曾经这样做过,并且非常喜欢它——将 JSON schema 作为事实依据和验证输入的方式,并从中推导出类型。此外,JSON schema 周围有许多现有的工具。
此外,虽然这个设计关注于设置,但与此相关的、可能可以用相同的工具链解决的问题是扩展主机通信。目前,与扩展主机进行的通信主要通过 JSON 序列化完成,并附带一些特殊处理。正如我在 #133200 中提到的,我们当前在这里拥有的抽象是有漏洞的,即使不偏离 JSON,也可能存在 benefits ,提前了解序列化数据的形状。如果我们使用一个为协议类型提供运行时信息系统,就有机会改进。

j7dteeu8

j7dteeu84#

我喜欢这个想法。我认为@hediet/semantic-json,我在这次迭代的早期ssh工作中使用了很多次,非常适合需求——我们不能将其用作外部库而不是内部库吗?显然有些东西需要扩展,但这似乎是一件可以重复利用的事情。
io-ts似乎更笨重,第一眼我就不太喜欢它的API。

fd3cxomn

fd3cxomn5#

我们能否将其用作外部库而非内部库?显然有些东西需要扩展,但这似乎可以重用。
我建议复制@hediet/semantic-json的设计并将其适应VS Code的具体需求。我在工作中使用个人库时感到不太舒服;)
我喜欢VS Code不使用许多库,而且我们可以轻松更改实用程序。
io-ts似乎更庞大,第一眼我就不喜欢它的API。
这就是为什么我从semantic-json开始的原因。io-ts类型在运行时也无法反映,这是生成json-schema所必需的。

f5emj3cl

f5emj3cl6#

@dbaeumer指出了一个重要问题:TypeScript在d.ts文件中生成的类型是什么样的?
有两种方法。

方法1:定义反序列化器,推断TypeScript类型(如hediet/semantic-json和io-ts所使用的)

export const minimapSettingsDeserializer1 = objectDeserializer1({
    properties: {
        /**
* The side.
*/
        side: prop(enumDeserializer('right', 'left'), { defaultValue: 'left' })
    }
});
export type MinimapSettings1 = typeof minimapSettingsDeserializer1.T;

生成的类型:

export declare const minimapSettingsDeserializer1: Deserializer<{
    side: string;
}>;
export declare type MinimapSettings1 = typeof minimapSettingsDeserializer1.T;

方法2:显式定义TypeScript类型,强制匹配反序列化器

export interface MinimapSettings2 {
    /**
* The side.
*/
    side: 'right' | 'left';
}

export const minimapSettingsDeserializer2 = objectDeserializer2<MinimapSettings2>({
    properties: {
        /**
* The side.
*/
        side: prop(enumDeserializer('right', 'left'), { defaultValue: 'left' })
    }
});

生成的类型:

export interface MinimapSettings2 {
    /**
* The side.
*/
    side: 'right' | 'left';
}
export declare const minimapSettingsDeserializer2: Deserializer<MinimapSettings2>;

TypeScript Playground链接
我认为由于缺少JS Docs(我们需要用于编辑器选项),只有方法2是可行的。

相关问题