-
Notifications
You must be signed in to change notification settings - Fork 479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: add support for polymorphism #51
Comments
it looks like the library does not support inheritance and use of interfaces within the classes. Am I wrong? |
Doesn't the following work for you? export abstract class Base {
@Exclude()
@Type(() => Date)
createdAt: Date
}
export class User extends Base {
public id: number;
private firstName: string;
private lastName: string;
private password: string;
setName(firstName: string, lastName: string) {
this.firstName = firstName;
this.lastName = lastName;
}
@Expose()
get name() {
return this.firstName + " " + this.lastName;
}
} |
Hi, that works, but the structure below does not: class User {
firstName: string;
lastName: string;
age: number;
@Type(() => Item)
items: Item[];
getFullName() { return this.firstName + " " + this.lastName; }
}
class Item {
id: number;
name: string;
}
class Book extends Item {
noOfPages: number;
public hi() { return this.id + ", " + this.name + ", " + this.noOfPages; }
}
class Toy extends Item {
category: string;
public hi() { return this.id + ", " + this.name + ", " + this.category;}
} The library does not keep the "TYPE" of instance of the Item, hence, cannot go back (plain to class and then class to plain) it('should return plain for User with proper types', () => {
let user: User = new User();
user.firstName = "Mustafa";
user.lastName = "Ekim";
user.age = 20;
let myBook: Book = new Book();
myBook.id = 10;
myBook.name = "Peter Pan";
myBook.noOfPages = 79;
let myToy: Toy = new Toy();
myToy.id = 14;
myToy.name = "cubuk";
myToy.category = "boys";
user.items = [myBook, myToy];
console.log(toPlain(user))
}) That is what I get: User {
firstName: 'Mustafa',
lastName: 'Ekim',
age: 20,
items:
[ Item { id: 10, name: 'Peter Pan', noOfPages: 79 },
Item { id: 14, name: 'cubuk', category: 'boys' } ] } |
Please can you format your code with three backstick (`) instead of one and also add I dont understand what do you mean by
Can you provide some more code examples? |
Hi, did you the time to check whether polymorphic inheritance works somehow? |
In that case we need an discriminator option like this: http://jmsyst.com/libs/serializer/master/reference/annotations#discriminator. It would be very useful to have it |
Hey, sorry for the late reply. You are right, this is not supported at the moment. While we cannot make this work "auto-magically" a What signature do you expect to have for such functionality? // this is a type guard, special type in ts,
// but basically it is a function which have to return a boolean value
function isBook(item: Book | Toy): item is Book {
return (<Book>item).noOfPages !== undefined;
}
function isToy(item: Book | Toy): item is Toy {
return (<Toy>item).category !== undefined;
}
// ....
class User {
firstName: string;
lastName: string;
age: number;
@Type(() => ({ [Book]: isBook, [Toy]: isToy }))
items: Item[];
getFullName() { return this.firstName + " " + this.lastName; }
} I think I am on vacation now, so I wont be able to look into this for a few more days, but I think it's a good thing to have, so I will pin this tab and look into this when I am back to work. |
Hi, what you offer seems flexible to me. However if there are lots of types, then the list below will be a problem: function isBook(item: Book | Toy | ... | ... | ... | ... ): item is Book {
return (<Book>item).noOfPages !== undefined;
} What mongodb does, it creates a new property when converting a class to a json file: __type |
I have around with it, and I had some problems with that format. Dont remember what :( But some changes will be needed to that signature.
i dont like that, how about when I want to init a class from user created content? I dont want to attach a type (internal representation) to any publicly sent data. |
It seems to me like there will be a lot of coupling with the model presentation and the code. Similarly, how Jackson (Java JSON/Object transformer) solves like this: @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "__type")
@JsonSubTypes({ @JsonSubTypes.Type(value = LinkCollector.class, name = "LinkCollector"), @JsonSubTypes.Type(value = UniqCollector.class, name = "UniqCollector"), @JsonSubTypes.Type(value = UserCollector.class, name = "UserCollector") })
public abstract class Collector implements TestResponsesContainer {
... Above is, 1 abstract class (Collector) and 3 different classes (LinkCollector, UserCollector, UniqCollector) that implement it. The annotations define the property name: __type and the possible subtype classes. It checks the given property and use the class associated |
You may also check ta-json |
Hi guys, |
Hi @sr-hosseyni! My first attempt to do this failed and I haven't spent any more time on it, so it definitely needs work. |
Hi @NoNameProvided , Have you created branch ? I like to try it. |
Hi! I didn't have a working solution. Also in the meantime, my Mac died so I needed to switch to another one, so I even lost what I have written, but that is not a big deal because it was nothing special or worthy anyway. I would not even near to make it work with the proposed signature and I am not even sure if it's possible that way. |
I came up with a little bit different format / way. I opened a PR for it: |
Thanks, @janis91! I will review it. |
I was able to write this annotation that can be used to deserialize polymorphic types. export abstract class Animal {
type: string;
}
export class Dog extends Animal {
public static typeChecker = (x => x.type === 'dog') as TypeChecker<Animal, Dog>;
meow() { console.log('meow'); }
}
export class Cat extends Animal {
public static typeChecker = (x => x.type === 'cat') as TypeChecker<Animal, Cat>;
woof() { console.log('woof'); }
} export type TypeChecker<BaseType extends object, ExtendedType extends BaseType = BaseType> = (x: BaseType) => x is ExtendedType;
export type TransformPolymorphicType<BaseType extends object, ExtendedType extends BaseType = BaseType> =
ClassType<ExtendedType> & { typeChecker: TypeChecker<BaseType, ExtendedType> };
export type TransformPolymorphicTypeMap<BaseType extends object> =
TransformPolymorphicType<BaseType>[];
export function TransformPolymorphic<BaseType extends object>(types: TransformPolymorphicTypeMap<BaseType>) {
return (target: any, key: string) => {
const metadata = new TransformMetadata(target.constructor, key, (value, obj, transformType) => {
const src: any[] = obj[key];
if (!src) return value;
var executor = new TransformOperationExecutor(TransformationType.PLAIN_TO_CLASS, {});
if (Array.isArray(src)) {
return src.map((item, index) => {
const type = types.find(x => x.typeChecker(item));
if (!type) {
console.error('Could not find polymorphic type for item:', item);
return value[index];
}
return executor.transform(null, item, type, undefined, undefined, undefined);
});
} else {
const type = types.find(x => x.typeChecker(src));
if (!type) {
console.error('Could not find polymorphic type for item:', src);
return value;
}
return executor.transform(null, src, type, undefined, undefined, undefined);
}
}, { toClassOnly: true });
defaultMetadataStorage.addTransformMetadata(metadata);
};
} Then you can use it somewhere like this: export default class Farm {
@TransformPolymorphic<Animal>([Dog, Cat])
posterAnimal: Animal;
@TransformPolymorphic<Animal>([Dog, Cat])
animals: Animal[];
} |
Since i did not want to create a fork i created this way based on @olee 's approach which works without needing any class-transformer internals. import { plainToClass, Transform } from 'class-transformer'
import { flatten } from 'lodash'
import { TransformationType } from 'class-transformer/TransformOperationExecutor'
import * as _ from 'lodash'
interface ClassType<T> { new (...args: any[]): T }
export type TypeChecker<BaseType extends object, ExtendedType extends BaseType = BaseType> = (x: BaseType) => x is ExtendedType
export type TransformPolymorphicType<BaseType extends object, ExtendedType extends BaseType = BaseType> =
ClassType<ExtendedType> & { typeChecker: TypeChecker<BaseType, ExtendedType> }
export type TransformPolymorphicTypeMap<BaseType extends object> = TransformPolymorphicType<BaseType>[]
/**
* Transforms simple and array properties if type's typeChecker returns true
*/
export function TypePoly<BaseType extends object>(types: TransformPolymorphicTypeMap<BaseType>) {
return Transform((value: BaseType | BaseType[], obj, transformationType: TransformationType) => {
const values = flatten([value]).map(v => {
for (const type of types) {
if (!!value && type.typeChecker(v)) {
return plainToClass(type, v)
}
}
throw new Error('TypePoly failed to identify type of plain object')
})
return _.isArray(value) ? values : values[0]
})
} It works the same way as @olee 's. export default class Farm {
@TypePoly<Animal>([Dog, Cat])
posterAnimal: Animal;
@TypePoly<Animal>([Dog, Cat])
animals: Animal[];
} Depending on your needs the Type decorator may be sufficent though as it's optional parameter function already comes with enough context information to implement your own polymorphic behaviour suited to your needs. Here a simple example: export default class Farm {
@Type((ops)=> [opts.object[opts.property]].animaltype === 'Dog'? Dog : Cat ?)
posterAnimal: Animal;
} I'll stick to TypePoly until typescript has better refelection support... Edit: I updated my previous code sample as it was buggy |
Hi @mustafaekim, have you had a chance to check out @janis91's implementation of polymorphism? I reckon since it was merged in, this issue can now be closed. |
Hi all! I believe we already have a PR merged for polymorphism, can someone take me up to speed why these changes are required and what benefits they bring which is not provided by the current implementation? |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Hi, does the library support polymorphism? If so, are there any examples I can look into?
The text was updated successfully, but these errors were encountered: