Comments (22)
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
from class-transformer.
You may also check ta-json
from class-transformer.
I was able to write this annotation that can be used to deserialize polymorphic types.
It uses a custom transformer to replace the deserialized array after work done.
I think it should also be easy to adjust it so it can also work without arrays.
To use it, you need to provide a type checker on each subclass like this:
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[];
}
from class-transformer.
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' } ] }
from class-transformer.
Please can you format your code with three backstick (`) instead of one and also add ts
to the end of the opening tag?
I dont understand what do you mean by
hence, cannot go back
Can you provide some more code examples?
from class-transformer.
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.
Please see his implementation of the Dog, Cat class above.
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
from class-transformer.
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.
from class-transformer.
it looks like the library does not support inheritance and use of interfaces within the classes. Am I wrong?
from class-transformer.
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;
}
}
from class-transformer.
Hi, did you the time to check whether polymorphic inheritance works somehow?
from class-transformer.
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 Discriminator
decorator or better extending the Type
decorator could be a good solution.
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 @Type(() => ({ [Book]: isBook, [Toy]: isToy }))
is an elegant way to solve this, thoughts?
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.
from class-transformer.
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
When it resolves back the model from json, it checks the __type property and instantiate it
from class-transformer.
Hi, what you offer seems flexible to me.
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.
What mongodb does, it creates a new property when converting a class to a json file: __type
When it resolves back the model from json, it checks the __type property and instantiate it
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.
from class-transformer.
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
from class-transformer.
Hi guys,
I need this feature in my personal project.
Does it ready or do you need help to developing ?
from class-transformer.
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.
from class-transformer.
Hi @NoNameProvided ,
Have you created branch ? I like to try it.
from class-transformer.
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.
from class-transformer.
I came up with a little bit different format / way. I opened a PR for it:
#125
from class-transformer.
Thanks, @janis91! I will review it.
from class-transformer.
My custom wrapper - https://github.com/sergeytkachenko/typescript-serialisation/blob/main/src/examples/chart-series.example.ts
from class-transformer.
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?
from class-transformer.
Related Issues (20)
- fix: exposeDefaultValues does not work HOT 2
- Renamed property does not exist on type Class HOT 2
- Expose properties with different name using a callback HOT 1
- question: How to expose attr when plain object no define HOT 1
- question: How to ignore Transform decorator when using plainToInstance HOT 2
- question: Getting `ReferenceError: Cannot access 'XXX' before initialization` in class circular dependency with Jest & Typescript HOT 3
- question: Constructors validation seems to break when using plainToInstance HOT 1
- Include @IsOptional values to the body if they are missing with default value HOT 2
- fix: Can't use instanceToPlain with Nitro.js event. HOT 4
- External Values are not stripped. HOT 4
- fix: plainToClass cannot handle URL values HOT 5
- feature: function support of options.discriminator.subTypes HOT 2
- plainToClassFromExist is Deprecated what is the replacement for generics? HOT 2
- Cannot transform string to number HOT 7
- question: Is class-transformer really thought to be used in the backend? HOT 4
- question: Options in @Transform Decorator Do Not Reflect Those Passed to plainToInstance in class-transformer HOT 2
- question: Casting Json to the required datatype HOT 2
- instanceToPlain not working properly using nestjs HOT 3
- Transform decorator in class-transformer does not wait for asynchronous function and returns a Promise instead of the transformed value HOT 3
- fix: Expose not working correctly with toClassOnly HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from class-transformer.