网站搜索

typescript - io-ts:使用字面量联合,在对象中定义必需和可选键


我正在尝试使用 io-ts 定义一个新的编解码器。

完成后形状应如下所示:

type General = unknown;
type SupportedEnv = 'required' | 'optional'

type Supported = {
  required: General;
  optional?: General;
}

(注意:目前General的形状并不重要)

这里的关键是我想根据General和SupportedEnv推导类型。

目前,我有类似这样的东西:

const GeneralCodec = iots.unknown;

const SupportedEnvCodec = iots.union([
  iots.literal('required'),
  iots.literal('optional'),
]);

const SupportedCodec = iots.record(SupportedEnvCodec, GeneralCodec)

type Supported = iots.TypeOf<typeof SupportedCodec>; 

类型 Supported 具有 两个必需的键:

type Supported = {
  required: General;
  optional: General;
}

我如何才能使 optional 真正可选?

我尝试过使用交集和部分类型……但我无法弄清楚 iots.record 的语法。

这是可能的吗?我需要以不同的方式思考这个问题吗?

答案#1

更新

创建一个映射函数 partialRecord(利用 io-ts 对 fp-ts 的同伴依赖),我们可以达到我们一直想要的目标。

import { map } from 'fp-ts/Record';
import * as iots from 'io-ts';

export const partialRecord = <K extends string, T extends iots.Any>(
  k: iots.KeyofType<Record<string, unknown>>,
  type: T,
): iots.PartialC<Record<K, T>> => iots.partial(map(() => type)(k.keys));

const GeneralCodec = iots.unknown;

const SupportedEnvCodec = iots.keyof({
  required:null,
  optional:null,
});

type SupportedEnv = iots.TypeOf<typeof SupportedEnvCodec>;
type RequiredEnv = Extract<SupportedEnv, 'required'>;

const RequiredEnvCodec: iots.Type<RequiredEnv> = iots.literal('required');

type OtherEnvs = Exclude<SupportedEnv, RequiredEnv>;
const OtherEnvsCodec: iots.KeyofType<Record<OtherEnvs, unknown>> = iots.keyof({optional:null});

const OtherEnvsRecordCodec = partialRecord<
  OtherEnvs,
  typeof GeneralCodec
>(OtherEnvsCodec, GeneralCodec);

const SupportedCodec = iots.intersection([
  iots.record(RequiredEnvCodec, GeneralCodec),
  OtherEnvsRecordCodec,

]);

type Supported = iots.TypeOf<typeof SupportedCodec>;

这产生了所需的类型:

type Supported = {
    required: unknown;
} & {
    optional?: unknown;
}

原始答案

我最接近的方法是添加一个额外的间接级别,这很不幸。

例如:

const GeneralCodec = iots.unknown;

const SupportedEnvCodec = iots.union([
  iots.literal('required'),
  iots.literal('optional'),
]);

type SupportedEnv = iots.TypeOf<typeof SupportedEnvCodec>;
type RequiredEnv = Extract<SupportedEnv, 'required'>;

const RequiredEnvCodec: iots.Type<RequiredEnv> = iots.literal('required');

type OtherEnvs = Exclude<SupportedEnv, RequiredEnv>;

const OtherEnvsCodec: iots.Type<OtherEnvs> = iots.union([
  iots.literal('optional'),
]);

const SupportedCodec = iots.intersection([
iots.record(RequiredEnvCodec, GeneralCodec),
iots.partial({
  env: iots.record(OtherEnvsCodec, GeneralCodec),
}),
]);

type Supported = iots.TypeOf<typeof SupportedCodec>; 

这会产生以下类型:

type Supported = {
    required: unknown;
  } & {
    env?: {
        optional: unknown;
    } | undefined;
}

哪个……接近——尽管我真的希望我不需要那额外的层,但它确实以一种迂回的方式回答了我最初的问题。