Я обычно определяю типы и затем извлекаю из них обобщенный c код c, а не создаю кодеки явно.
Например: сначала определите ваши типы с некоторыми данными и закодируйте их отношения ( значения элемента списка и объединения):
type Type = Integer | List<any> | Union<any>;
interface Integer {
type: 'integer';
}
interface List<T extends Type> {
type: 'list';
item: T;
}
type UnionValues = Type[];
interface Union<T extends UnionValues> {
type: 'union';
values: T;
}
Также полезно предоставить помощников для создания следующих типов:
const integer: Integer = { type: 'integer' };
const list = <T extends Type>(item: T): List<T> => ({
type: 'list',
item
});
const union = <T extends UnionValues>(...values: T): Union<T> => ({
type: 'union',
values
});
Затем можно написать рекурсивную функцию отображения типов. Это сопоставит Type
с соответствующим JS типом:
type Decode<T> =
// terminal recursion: Integer is represented as a number
T extends Integer ? number :
// extract the Item from the list and construct an Array recursively
T extends List<infer I> ? Decode<I>[] :
// union is an array of types, so loop through and decode them
T extends Union<infer U> ? {
[i in Extract<keyof U, number>]: Decode<U[i]>;
}[[Extract<keyof U, number>]] :
never
;
Определите ваш код c как чтение из Type => Value
:
interface Codec<T extends Type, V> {
type: T;
read(value: any): V;
}
Написать функцию который сопоставляет экземпляр типа с его кодом c:
function codec<T extends Type>(type: T): Codec<T, Decode<T>> {
// todo
}
Теперь вы можете безопасно отобразить между вашей системой типов и JS типами:
const i = codec(integer);
const number: number = i.read('1');
const l = codec(list(integer));
const numberArray: number[] = l.read('[1, 2]');
const u = codec(union(integer, list(integer)));
const numberOrArrayOfNumbers: number | number[] = u.read('1');
Я пытался воссоздать ваш пример, где разработчики пишут кодеки, которые кодируют их тип. Я думаю, что это слабо, что вы пытаетесь сделать. Это было немного сложнее, потому что вам нужно отобразить кортежи.
Целочисленный код c - это прямое отображение целого числа -> число.
class IntegerCodec implements Codec<Integer, number> {
public readonly type: Integer = integer;
public read(value: any): number {
return parseInt(value, 10);
}
}
ListCode c рекурсивно вычисляет отображение List -> ItemValue []
namespace Codec {
// helper type function for grabbing the JS type from a Codec<any, any>
export type GetValue<C extends Codec<any, any>> = C extends Codec<any, infer V> ? V : never;
}
// this is where we recurse and compute the Type and JSType from the provided Item codec
class ListCodec<Item extends Codec<any, any>> implements Codec<List<Item['type']>, Codec.GetValue<Item>[]> {
public readonly type: List<Item['type']>;
constructor(public readonly item: Item) {
this.type = list(item.type);
}
public read(value: any): Codec.GetValue<Item>[] {
return value.map((v: any) => this.item.read(v));
}
}
Объединение немного сложнее, потому что нам нужно отобразить набор кодеков для вычисления типов и значений.
Первая утилита: Compute Тип объединения из кортежа кодеков
type ComputeUnionType<V extends Codec<any, any>[]> = Union<Type[] & {
[i in Extract<keyof V, number>]: V[i]['type']
}>;
Вторая утилита: Вычисление типа объединения JS из кортежа кодеков:
type ComputeUnionValue<V extends Codec<any, any>[]> = {
[i in Extract<keyof V, number>]: Codec.GetValue<V[i]>;
}[Extract<keyof V, number>];
Затем мы пишем код UnionCode c, который рекурсивно вычисляет тип и JS тип объединения:
class UnionCodec<V extends Codec<any, any>[]> implements Codec<
ComputeUnionType<V>,
ComputeUnionValue<V>
> {
public readonly type: ComputeUnionType<V>;
constructor(public readonly codecs: V) {}
public read(value: any): ComputeUnionValue<V> {
throw new Error("Method not implemented.");
}
}
Теперь ваш пример проверки типов:
const ic = new IntegerCodec();
const lc: ListCodec<IntegerCodec> = new ListCodec(new IntegerCodec());
const uc: UnionCodec<[ListCodec<IntegerCodec>, IntegerCodec]> = new UnionCodec([lc, ic]);
const listValue: number | number[] = uc.read('1');