来自 JSON 模式的 TypeScript 交集(非联合)类型

TypeScript Intersection (not Union) Type from JSON Schema

我正在跟进 :

我有以下架构:

const schemas = {
  POST: {
    $schema: 'https://json-schema.org/draft/2019-09/schema#',
    $id: 'https://api.netbizup.com/v1/health/schema.json',
    type: 'object',
    properties: {
      body: {
        type: 'object',
        properties: {
          greeting: {
            type: 'string',
          },
        },
        additionalProperties: false,
      },
    },
    required: ['body'],
  } as const,
  PUT: {
    $schema: 'https://json-schema.org/draft/2019-09/schema#',
    $id: 'https://api.netbizup.com/v1/health/schema.json',
    type: 'object',
    properties: {
      body: {
        type: 'object',
        properties: {
          modified: {
            type: 'boolean',
          },
        },
        required: ['modified'],
        additionalProperties: false,
      },
    },
    required: ['body'],
  } as const,
};

我正在使用 json-schema-to-ts 包中的 FromSchema 来推断上面 const 对象的每个 body 属性的类型。

我需要实现的是 POSTPUT 主体的交集类型。上一个问题中提供的答案非常适合创建 TypeScript Union。问题当然是当我尝试访问 body 的成员时出现错误,因为并非所有属性都始终存在于每个 body.

我什至想知道 TypeScript 是否允许我们验证如果我们从“选择的”body 对象访问一个属性,那么它将不允许我们访问另一个。

我创建了一个 Playground 来说明我的问题。

附件

答案部分有效的游乐场:https://www.typescriptlang.org/play?target=7&module=6&pretty=true#code/JYWwDg9gTgLgBAbzgMShEBlAxgCwKYgCGcAvnAGZohwDkAVgM4QB2AtA7gYazBDwzQDcAWABQoSLERwAggAUAkgHFCMPAHdCATzloAHloCiANzzMYANQBMAdWAwcAKRsAVGQFcH0YAC88UUgoqWkJ1BlYAG0IQACMAE0IhMTEsFgZ4DnwiBjgAXkQxODg5AHkMFwAuAtEiooASTK4qmhwYGDAGCoB6LsYWdk4iADpoAHMuuKhCchguqwAGAEYATlZ55a7GogBiGgAaQtq64Djm1vbOnsIwYCHmPBgY33cwIdSQLuNFrvxCCIdNoNCEM+sx9ocijAtGA8M0IDE6HgsDBwTVamA0DDYMA8J1qrVajEIHEtFUEBCCXAoTC4QikSiDmjKXAMRAsTAcXjyUzmXBRlA8A9gMxRmSKbyqdDYbR0lBhaNURKiiRGRKVeLaoQ4nF7MAWH9dGz-BzcVVyH8GHhVZT1TzbQSBQBHdzAAWnOAAbRoRJJNAAutayIQcqlmOlrXIAKqVfFHLaEM5tDrdXpMNjxkZQcaTaazBYrNYbeO7a31E6Ji4p663e6PZ6vd6fb6-f44QFZYGgxW1anSmjwxHI7tFVnszlinlFH2k2PM3u0wcMjUjzHG8ez3kgYnAcg493cpU9qXNIkQCJ4Qhg0u8+0368OvDO114d1erc63cv-33zXa3X6iJDTHU0KAtK0NVvZVrydF03SqL1p2-Q4gxDNIYEZEgRFEMRezgBRmDATxsA7ABpPAtDyWglEMFwaDgAAfWhSnKOjGJoKNaIYpiZBcABhAAJVjaAAEUMAAZGjDCSbDRFw-DCJgYiuByfJuQASA9ABrci4GFPCCKIoEyK0P0yRZVdsRApBpyqdxmE05gIHUZhAkwsQ3Jkno4BsHBVBoHJ1DQEU4HUewcDgFwlKIOA8D0NRmB1YL5MMjsckIIlTAAfjELyFCpHBhU0-LgByEq4GIGJ3FGXSXJcKVsDlMB4HUfABTge50hfcyjVgCjCCwLBcRyFgarUKBzUGrpcIHelJRhHIBQYdx-lKlz-DQKAGCGHCpTgAAhYktCiwgAB5IqBGK4rMOIciQLTyKqWV5VM6RRzXKy4BsuA7IcpyXLIAGAD5KPOjt7q0CByAi46-S9N7LIEWHvUO-0sJyro4HOZMenSfrNIgUxxoiJy3nQLpnVxDk0i6ABWGnFisAB2eZ5gAFh+JyeD4GApjDchoBAVg7L1Nhey5ngXnPHgpWSUQvIgcKiQouIWBoeAVeKhgdphOBI2YEWXAgfCxstZERZOyNgdyQ4AApI0u+KbvK5gKMyuAbc0qpIwASjyYHjAgE44Cqe5Cd92LHZyG2PaqYVyH8PDfdyf3A7iX23bykO8EJ7W8DgMTgxgEpyDOq3Dj1g2jfMfxTap5gzod660pduA3ZtpPgZcYP2uz-xgYjpv3Y79244TgAldO4DH7vQ-8WWvMi1mhnmABqXPincBgcAbgeEubrQPQDOALCtz0hnPlw9mPv00bljHF6GRY19k3aK5YQ2XElvAzqvsTKIL9IxdS5XwAHKUQ9C4P0jc96elnlAKBbsebuDzmaMCZc0RILzrvJ2YC3aH27nITe2837MA-l-E6hg9BYAiO4OI39L750BoDX+gNZa4V4iwQmMAZARAiD-Ke0CnZjwvCrZgEQtAyCgFMLQJ0fqOWcqfQ+p8u7YJyB6UeARkCunDHAc+QwNFT0pgguAHDmBcJ4Xw4ROiPR6LHlfVA6BjonS0ZtGAgM-TAyqGPW+uEHEgAscdBgFtT6mPMbwi2+t34QE-mAc8wTAYemYO4WI-gb5sN2i4A6JJKLWUOlUPxASgRBKyUdIEJ1eyQzgPGBgTDBCkFlqGdILIIDpBKVUTJh0cmfTydIfkgoOQimaPxPAvCIAAEI6IAywo0+Al4IAOH8BYP4Jw5AtJgG0iKJSulfSQFMlIaEWSeA2R07JqlukkjMu+Hce4qiYNcj43aJT+KXjiOeKAZ0SnHVPjbMQale7mCqCUEA9gTryGUKoDQ2hDQGBMGYSwtgwrODcJ4HA3g-BQCvsjX0wMABk1Q1JqS+ic0pHZBG3TIG7ApvDAknU+WU4lXzPFVJ5vKLCalbTDwDicW+MzzlaGeQlN5lEzoCtef4OlpL8hyL+oDH5og1K+UFf4KoTyXlvJFWq8Vh0vliGHkgAUMB3BQH+g0g5iqxUBHyNOUV6qqURBpRKrg5SpSVOqUw2V5UGBaGYFgd2-yYB6sOI0s8eAhjE1GDbCpUN-VDGnN7LCJBvZAA

It seems as if FromSchema cannot handle a union of the schemas. You can get around this by turning the union into a tuple, converting each schema in the tuple with FromSchema, then converting it back to a union.

为此我们首先需要 post

我在这里将 TuplifyUnion 重命名为 UnionToTuple

然后让我们编写一个类型来转换我们希望转换的模式联合。

我们需要将这个并集转换为一个元组,然后分别转换每个模式,最后将生成的元组转回一个并集。

type FromAllSchemas<U> = ConvertAll<UnionToTuple<U>>[number];

这是用 UnionToTuple[number] 完成的。

现在写ConvertAll.

type ConvertAll<T, R extends ReadonlyArray<unknown> = []> =
    T extends [infer First, ...infer Rest]
        ? ConvertAll<Rest, [...R, FromSchema<First>]>
        : R;

首先我们将第一个元素和其余元素推断为一个元组。

然后我们使用 FromSchema 将第一个元素转换为类型,并将其放入结果中。然后我们再次“调用”ConvertAll,这次使用其余元素和新结果。

最后,如果我们无法推断出第一个元素 T 为空,那么我们只 return 结果 R.

Playground