我正在尝试实现一个名为Serialised<T>
的接口,以表示给定类的公共非功能属性的递归序列化。
我的current implementation使用type-level programming并可以使用TypeScript 3.0,但是感觉可以简化它以利用TS 3.3中的新语言功能,并且不会有黑客的味道。 / p>
假设我们已经有一个名为TinyType
的基类,并且具有方法toJSON
。
该方法根据以下规则对TinyType
的任何子类进行序列化,我希望Serialised<T>
接口表示该子类,以便可以将子类定义为:
class MyClass extends TinyType {
toJSON(): Serialised<MyClass> {...}
}
当TinyType包装单个值时,它将序列化为该单个值将被序列化为的值。
对于基元,TinyType
的序列化表示就是基元本身:
class Name extends TinyType {
constructor(public readonly name: string) {}
}
new Name('Bob').toJSON() === 'Bob'; // toJSON(): string
class Age extends TinyType {
constructor(public readonly age: string) {}
}
new Age(42).toJSON() === 42 . // toJSON(): number
但是,我们也可能遇到单值TinyType包装另一个单值TinyType的情况,在这种情况下,第一个示例中的规则将递归地应用:
class Integer extends TinyType {
constructor(public readonly value: number) {
// some logic that ensures that value is an integer...
}
}
class AmountInCents extends TinyType {
constructor(public readonly amountInCents: Integer) {}
}
class Credit extends TinyType {
constructor(public readonly amount: AmountInCents) {}
}
new Credit(new AmountInCents(new Integer(100))).toJSON() === 100
// toJSON(): number
当TinyType包装多个值时,应将其序列化为JSON对象,并使用键表示TinyType的公共非功能属性,并使用值表示其序列化版本。
class Timestamp extends TinyType {
constructor(public readonly value: Integer) {}
}
class Credit extends TinyType {
constructor(public readonly amount: AmountInCents) {}
}
class CreditRecorded extends TinyType {
constructor(
public readonly credit: Credit,
public readonly timestamp: Timestamp,
) {}
}
new CreditRecorded(
new Credit(new AmountInCents(new Integer(100))),
new Timestamp(new Integer(1234567)),
).toJSON() === { credit: 100, timestamp: 1234567 }
到目前为止,我的研究表明该解决方案可以利用:
我当然可以将toJSON()
定义为返回JSONValue
,并避免将类映射到其序列化表示形式的麻烦,但是感觉可以在这里做得更好?
欢迎提出建议!
答案 0 :(得分:2)
这应该可以正常工作:
type NotFunctions<T, E extends keyof T> = {
[P in Exclude<keyof T, E>]-?: T[P] extends Function ? never : P
}[Exclude<keyof T, E>]
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type Unwrap<T> = T extends { toJSON(): infer U } ? U : T;
type PickAndUnwrap<T, K extends keyof T> = {
[P in K] : Unwrap<T[P]>
}
type SimpleOrComplex<T, E extends keyof T> = NotFunctions<T, E> extends UnionToIntersection<NotFunctions<T, E>>?
PickAndUnwrap<T, NotFunctions<T, E>>[NotFunctions<T, E>] :
PickAndUnwrap<T, NotFunctions<T, E>>
type Id<T> = T extends object ? {} & { [P in keyof T] : T[P]} : T
class TinyType {
public toJSON(): Id<SimpleOrComplex< this, keyof TinyType>> {
return null!;
}
}
class Name extends TinyType {
constructor(public readonly name: string) {
super();
}
}
new Name('Bob').toJSON() === "" // toJSON(): string
class Age extends TinyType {
constructor(public readonly age: number) {
super()
}
}
new Age(42).toJSON() === 42 // toJSON(): number
class Integer extends TinyType {
constructor(public readonly value: number) {
super();
}
}
class AmountInCents extends TinyType {
constructor(public readonly amountInCents: Integer) {
super();
}
}
class Credit extends TinyType {
constructor(public readonly amount: AmountInCents) {
super()
}
}
new AmountInCents(new Integer(100)).toJSON
new Credit(new AmountInCents(new Integer(100))).toJSON() === 100
// toJSON(): number
class Timestamp extends TinyType {
constructor(public readonly value: Integer) {
super();
}
}
class CreditRecorded extends TinyType {
constructor(
public readonly credit: Credit,
public readonly timestamp: Timestamp,
) {
super();
}
}
new CreditRecorded(
new Credit(new AmountInCents(new Integer(100))),
new Timestamp(new Integer(1234567)),
).toJSON() === { credit: 100, timestamp: 1234567 }
class Person extends TinyType {
constructor(
public readonly name: Name,
public readonly creditRecord: CreditRecorded,
public readonly age: Integer) {
super();
}
}
new Person(new Name(""), new CreditRecorded(
new Credit(new AmountInCents(new Integer(100))),
new Timestamp(new Integer(1234567)),
), new Integer(23)).toJSON() // { readonly name: string; readonly creditRecord: { readonly credit: number; readonly timestamp: number; }; readonly age: number; }
仅需注意一些事项,并未进行广泛的测试,因此,如果编译器认为类型太复杂,您可能会更深入地了解any
。 Id
只是出于装饰性原因而扁平化类型,如果遇到问题,只需使用Id<T> = T
并查看taht是否可以解决该问题。
如果您有任何问题让我知道,我会尽力回答,解决方案通常只是像您想的那样直接应用映射类型和条件类型。