展平嵌套值对象的通用类型
Generic type of flatten nested value objects
我正在尝试为 domain/aggregate 和实体定义通用类型,这将使我能够构建包含多个域的聚合。总而言之,这将导致嵌套值对象 类.
为了简化 API 响应,我想提供一个 toObject() 函数,它会 return 一个带有字符串(或者更好;ValueType)的对象。
在下面的示例中,我希望 order.toObject() 到 return 最后指定的类型和对象。
我还希望能够为 BaseDomain.toObject()
定义一个 recursive/deep 通用类型
有什么想法吗?我欢迎反馈和想法
//// What I would like to solve /////
// Define a generic type that flatten a object by removing all class specific and only keeping the properties
type FlattenProperties<T> = any;
// My poor attempt
/*
type FlattenProperties<T> = T extends IBaseEntity<infer U> ? U
: T extends IBaseList<infer U>
? T['items'] extends IBaseDomain<infer Z>[] ? [...T['items']]: FlattenRecursiveEntityProperties<Z> //Not sure of how to recurse iterate a array
: U[]
: T extends object ? {[K in keyof T]: FlattenRecursiveEntityProperties<T[K]>}
: T;
*/
//// Helpers //////
interface IValue<ValueType> {
value: ValueType
}
interface IBaseEntity<ValueType> {
value: ValueType
}
abstract class BaseEntity<ValueType> implements IBaseEntity<ValueType> {
readonly properties: IValue<ValueType>;
constructor(value?: ValueType) {
this.properties = { value: value ?? {} as ValueType };
}
get value(): ValueType {
return this.properties.value;
}
}
class ObjectId extends BaseEntity<string> {
constructor(id?: string) {
super()
this.properties.value = id ?? 'GeneratedID';
}
}
interface IBaseList<ItemType> {
items: ItemType[]
}
abstract class BaseList<ItemType> {
readonly items: ItemType[];
constructor(items?: ItemType[]) {
this.items = items ?? [];
}
getItems() {
return this.items;
}
add(item: ItemType) {
this.items.push(item);
}
}
interface IBaseDomain<Interface> {
readonly properties: Interface;
readonly _id: ObjectId;
toObject(): FlattenProperties<Interface>
}
abstract class BaseDomain<Interface> implements IBaseDomain<Interface> {
readonly properties: Interface;
readonly _id: ObjectId;
constructor(object?: Interface, id?: ObjectId) {
this._id = id ?? new ObjectId('#a-generated-value#');
this.properties = object ?? {} as Interface;
}
get id() {
return this._id
}
toObject(): FlattenProperties<Interface> {
return {} as FlattenProperties<Interface>;
}
}
///// Definition of domains ///////
interface IProduct {
name: string;
description: string
}
class Product extends BaseDomain<IProduct> {
constructor(name: string, description: string) {
super({ name: name, description: description })
}
}
interface IOrderLine {
product: Product;
qty: number;
}
class OrderLine extends BaseDomain<IOrderLine> {
constructor(product: Product, qty: number) {
super({ product: product, qty: qty })
}
}
class OrderLines extends BaseList<OrderLine> { }
interface IOrder {
orderComment: string;
orderLines: OrderLines
}
class Order extends BaseDomain<IOrder> {
}
///// Example run /////
const toyTruck = new Product('Toy truck', 'Yellow plastic toy truck');
const toyDuck = new Product('Duck', 'Made famous by Duck Sauce');
export const order = new Order({
orderComment: 'Some new shiny toys',
orderLines: new OrderLines([new OrderLine(toyTruck, 1), new OrderLine(toyDuck, 100)])
});
const htmlResponse: Expected = order.toObject(); // Type = Expected
htmlResponse.orderLines[0].product.name; // Type = string
type Expected = {
orderComment: string,
orderLines: {
product: {
name: string,
description: string
},
qty: number
}[]
}
type Expected2 = {
orderComment: string,
orderLines: {
product: IProduct,
qty: number
}[]
}
declare var x: Expected;
declare var x: Expected2;
//declare var x: FlattenProperties<IOrder>; //TODO: Make valid
看起来 FlattenProperties<T>
应该是递归的 recursive conditional type, where the base cases are if T
is a primitive type or if T extends IBaseEntity<U>
for some U
. If T extends IBaseDomain<U>
or IBaseList<U>
for some U
then you can recurse into U
. Or if T
is an object type, you can map its properties。像这样:
type FlattenProperties<T> =
T extends IBaseEntity<infer U> ? U :
T extends IBaseDomain<infer U> ? FlattenProperties<U> :
T extends IBaseList<infer U> ? FlattenProperties<U>[] :
T extends object ? { [K in keyof T]: FlattenProperties<T[K]> } : T;
您在评论中提到您可能希望原始节点或 IBaseEntity
叶节点为 string
而不是其他类型,因此在这种情况下它看起来像:
type FlattenProperties<T> =
T extends IBaseEntity<infer U> ? string :
T extends IBaseDomain<infer U> ? FlattenProperties<U> :
T extends IBaseList<infer U> ? FlattenProperties<U>[] :
T extends object ? { [K in keyof T]: FlattenProperties<T[K]> } : string;
但现在我将使用第一个定义,因为它更笼统。让我们看看它是否有效:
type FPOrder = FlattenProperties<Order>
/* type FPOrder = {
orderComment: string;
orderLines: {
product: {
name: string;
description: string;
};
qty: number;
}[];
} */
这就是你想要的类型,所以看起来不错!
为了清楚地知道发生了什么,让我们“手动”部分评估FlattenProperties<Order>
:
FlattenProperties<Order>
变成
Order extends IBaseEntity<infer U> ? U :
Order extends IBaseDomain<infer U> ? FlattenProperties<U> :
Order extends IBaseList<infer U> ? FlattenProperties<U>[] :
Order extends object ? { [K in keyof Order]: FlattenProperties<Order[K]> } : T;
因为 Order
不是 IBaseEntity
我们转到第二行。 Order
是一个 IBaseDomain<IOrder>
,因此 U
被推断为 IOrder
并且类型变为
FlattenProperties<IOrder>
现在我们有
IOrder extends IBaseEntity<infer U> ? U :
IOrder extends IBaseDomain<infer U> ? FlattenProperties<U> :
IOrder extends IBaseList<infer U> ? FlattenProperties<U>[] :
IOrder extends object ? { [K in keyof IOrder]: FlattenProperties<IOrder[K]> } : T;
IOrder
既不是 IBaseEntity
,也不是 IBaseDomain
,也不是 IBaseList
。这是一个 object
,所以我们现在有
{ [K in keyof IOrder]: FlattenProperties<IOrder[K]> }
变成
{
orderComment: FlattenProperties<string>;
orderLines: FlattenProperties<OrderLines>
}
嗯 FlattenProperties<string>
只是 string
因为 string
是一个不扩展任何 IBase*
类型的原语。而 FlattenProperties<OrderLines>
变成 FlattenProperties<OrderLine>[]
因为 OrderLines extends IBaseList<OrderLine>
。所以现在我们有
{
orderComment: string;
orderLines: FlattenProperties<OrderLine>[]
}
我们可以继续前进,但希望您能看到这最终将如何评估为所需的类型。
我正在尝试为 domain/aggregate 和实体定义通用类型,这将使我能够构建包含多个域的聚合。总而言之,这将导致嵌套值对象 类.
为了简化 API 响应,我想提供一个 toObject() 函数,它会 return 一个带有字符串(或者更好;ValueType)的对象。
在下面的示例中,我希望 order.toObject() 到 return 最后指定的类型和对象。
我还希望能够为 BaseDomain.toObject()
定义一个 recursive/deep 通用类型有什么想法吗?我欢迎反馈和想法
//// What I would like to solve /////
// Define a generic type that flatten a object by removing all class specific and only keeping the properties
type FlattenProperties<T> = any;
// My poor attempt
/*
type FlattenProperties<T> = T extends IBaseEntity<infer U> ? U
: T extends IBaseList<infer U>
? T['items'] extends IBaseDomain<infer Z>[] ? [...T['items']]: FlattenRecursiveEntityProperties<Z> //Not sure of how to recurse iterate a array
: U[]
: T extends object ? {[K in keyof T]: FlattenRecursiveEntityProperties<T[K]>}
: T;
*/
//// Helpers //////
interface IValue<ValueType> {
value: ValueType
}
interface IBaseEntity<ValueType> {
value: ValueType
}
abstract class BaseEntity<ValueType> implements IBaseEntity<ValueType> {
readonly properties: IValue<ValueType>;
constructor(value?: ValueType) {
this.properties = { value: value ?? {} as ValueType };
}
get value(): ValueType {
return this.properties.value;
}
}
class ObjectId extends BaseEntity<string> {
constructor(id?: string) {
super()
this.properties.value = id ?? 'GeneratedID';
}
}
interface IBaseList<ItemType> {
items: ItemType[]
}
abstract class BaseList<ItemType> {
readonly items: ItemType[];
constructor(items?: ItemType[]) {
this.items = items ?? [];
}
getItems() {
return this.items;
}
add(item: ItemType) {
this.items.push(item);
}
}
interface IBaseDomain<Interface> {
readonly properties: Interface;
readonly _id: ObjectId;
toObject(): FlattenProperties<Interface>
}
abstract class BaseDomain<Interface> implements IBaseDomain<Interface> {
readonly properties: Interface;
readonly _id: ObjectId;
constructor(object?: Interface, id?: ObjectId) {
this._id = id ?? new ObjectId('#a-generated-value#');
this.properties = object ?? {} as Interface;
}
get id() {
return this._id
}
toObject(): FlattenProperties<Interface> {
return {} as FlattenProperties<Interface>;
}
}
///// Definition of domains ///////
interface IProduct {
name: string;
description: string
}
class Product extends BaseDomain<IProduct> {
constructor(name: string, description: string) {
super({ name: name, description: description })
}
}
interface IOrderLine {
product: Product;
qty: number;
}
class OrderLine extends BaseDomain<IOrderLine> {
constructor(product: Product, qty: number) {
super({ product: product, qty: qty })
}
}
class OrderLines extends BaseList<OrderLine> { }
interface IOrder {
orderComment: string;
orderLines: OrderLines
}
class Order extends BaseDomain<IOrder> {
}
///// Example run /////
const toyTruck = new Product('Toy truck', 'Yellow plastic toy truck');
const toyDuck = new Product('Duck', 'Made famous by Duck Sauce');
export const order = new Order({
orderComment: 'Some new shiny toys',
orderLines: new OrderLines([new OrderLine(toyTruck, 1), new OrderLine(toyDuck, 100)])
});
const htmlResponse: Expected = order.toObject(); // Type = Expected
htmlResponse.orderLines[0].product.name; // Type = string
type Expected = {
orderComment: string,
orderLines: {
product: {
name: string,
description: string
},
qty: number
}[]
}
type Expected2 = {
orderComment: string,
orderLines: {
product: IProduct,
qty: number
}[]
}
declare var x: Expected;
declare var x: Expected2;
//declare var x: FlattenProperties<IOrder>; //TODO: Make valid
看起来 FlattenProperties<T>
应该是递归的 recursive conditional type, where the base cases are if T
is a primitive type or if T extends IBaseEntity<U>
for some U
. If T extends IBaseDomain<U>
or IBaseList<U>
for some U
then you can recurse into U
. Or if T
is an object type, you can map its properties。像这样:
type FlattenProperties<T> =
T extends IBaseEntity<infer U> ? U :
T extends IBaseDomain<infer U> ? FlattenProperties<U> :
T extends IBaseList<infer U> ? FlattenProperties<U>[] :
T extends object ? { [K in keyof T]: FlattenProperties<T[K]> } : T;
您在评论中提到您可能希望原始节点或 IBaseEntity
叶节点为 string
而不是其他类型,因此在这种情况下它看起来像:
type FlattenProperties<T> =
T extends IBaseEntity<infer U> ? string :
T extends IBaseDomain<infer U> ? FlattenProperties<U> :
T extends IBaseList<infer U> ? FlattenProperties<U>[] :
T extends object ? { [K in keyof T]: FlattenProperties<T[K]> } : string;
但现在我将使用第一个定义,因为它更笼统。让我们看看它是否有效:
type FPOrder = FlattenProperties<Order>
/* type FPOrder = {
orderComment: string;
orderLines: {
product: {
name: string;
description: string;
};
qty: number;
}[];
} */
这就是你想要的类型,所以看起来不错!
为了清楚地知道发生了什么,让我们“手动”部分评估FlattenProperties<Order>
:
FlattenProperties<Order>
变成
Order extends IBaseEntity<infer U> ? U :
Order extends IBaseDomain<infer U> ? FlattenProperties<U> :
Order extends IBaseList<infer U> ? FlattenProperties<U>[] :
Order extends object ? { [K in keyof Order]: FlattenProperties<Order[K]> } : T;
因为 Order
不是 IBaseEntity
我们转到第二行。 Order
是一个 IBaseDomain<IOrder>
,因此 U
被推断为 IOrder
并且类型变为
FlattenProperties<IOrder>
现在我们有
IOrder extends IBaseEntity<infer U> ? U :
IOrder extends IBaseDomain<infer U> ? FlattenProperties<U> :
IOrder extends IBaseList<infer U> ? FlattenProperties<U>[] :
IOrder extends object ? { [K in keyof IOrder]: FlattenProperties<IOrder[K]> } : T;
IOrder
既不是 IBaseEntity
,也不是 IBaseDomain
,也不是 IBaseList
。这是一个 object
,所以我们现在有
{ [K in keyof IOrder]: FlattenProperties<IOrder[K]> }
变成
{
orderComment: FlattenProperties<string>;
orderLines: FlattenProperties<OrderLines>
}
嗯 FlattenProperties<string>
只是 string
因为 string
是一个不扩展任何 IBase*
类型的原语。而 FlattenProperties<OrderLines>
变成 FlattenProperties<OrderLine>[]
因为 OrderLines extends IBaseList<OrderLine>
。所以现在我们有
{
orderComment: string;
orderLines: FlattenProperties<OrderLine>[]
}
我们可以继续前进,但希望您能看到这最终将如何评估为所需的类型。