Class-transformer: question: using @Type with subclasses of the same class

Created on 22 Nov 2018  路  9Comments  路  Source: typestack/class-transformer

I've been trying to use @Type() with itself:

import { Type } from 'class-transformer';

class User {

  @Type(() => User, {
    discriminator: {
      property: 'type',
      subTypes: [
        { value: HappyFriend, name: 'happy' },
        { value: SadFriend, name: 'sad' },
      ],
    },
  })
  friend: HappyFriend | SadFriend;
}

class HappyFriend extends User {
  doHappy() {}
}

class SadFriend extends User {
  doSad() {}
}

but I can't because [ts] Class 'HappyFriend' used before its declaration. [2449] and [ts] Class 'SadFriend' used before its declaration. [2449]

Any idea what I can do here?

invalid question

Most helpful comment

@hedegitsinue

import { Transform, TransformationType, classToPlain, plainToClass, classToClass } from 'class-transformer';

function transformFn(value: User, obj, type) {
  if (type === TransformationType.PLAIN_TO_CLASS) {
    return User.fromObject(value);
  } else if (type === TransformationType.CLASS_TO_PLAIN) {
    return classToPlain(value);
  } else if (type === TransformationType.CLASS_TO_CLASS) {
    return classToClass(value);
  }
}

class User {
  type: string = '';

  name: string;

  @Transform(transformFn)
  friend: User;

  sayHi() {
    console.log(`${this.getMessage()}\n`);
  }

  getMessage() {
    return `My name is "${this.name}", I am a *${this.friendlyType()}*${this.friend ? ` and my friend says:\n${this.friend.getMessage()}` : '.'}`;
  }

  friendlyType() { return 'Base User'; }

  static fromObject(value) {
    switch (value.type) {
      case 'happy':
        return plainToClass(HappyUser, value);
      case 'sad':
        return plainToClass(SadUser, value);
      default:
        return plainToClass(User, value);
    }
  }
}

class HappyUser extends User {
  friendlyType() { return 'Happy User'; }
}

class SadUser extends User {
  friendlyType() { return 'Sad User'; }
}

plainToClass(User, {
  name: 'A',
  friend: {
    name: 'A\'s Friend',
    type: 'happy',
  }
}).sayHi();

plainToClass(User, {
  name: 'B',
  friend: {
    name: 'B\'s Friend',
    type: 'sad',
  }
}).sayHi();

plainToClass(User, {
  name: 'C',
  friend: {
    name: 'C\'s Friend',
    type: 'unknown',
    friend: {
      name: '2nd Level Friend',
      type: 'happy',
      friend: {
        name: '3rd Level Friend',
        type: 'another',
      }
    }
  }
}).sayHi();

All 9 comments

I managed to use @Transform() to do it. It calls a recursive function imported from another file which in turn imports the classes and uses a switch() to check. I was worried about a circular reference but apparently not.

Hello @patricknazar

I've encountered the same problem and still not able to find a proper solution. Can you please share the solution you came up with or at least the basics of the idea?

@hedegitsinue

import { Transform, TransformationType, classToPlain, plainToClass, classToClass } from 'class-transformer';

function transformFn(value: User, obj, type) {
  if (type === TransformationType.PLAIN_TO_CLASS) {
    return User.fromObject(value);
  } else if (type === TransformationType.CLASS_TO_PLAIN) {
    return classToPlain(value);
  } else if (type === TransformationType.CLASS_TO_CLASS) {
    return classToClass(value);
  }
}

class User {
  type: string = '';

  name: string;

  @Transform(transformFn)
  friend: User;

  sayHi() {
    console.log(`${this.getMessage()}\n`);
  }

  getMessage() {
    return `My name is "${this.name}", I am a *${this.friendlyType()}*${this.friend ? ` and my friend says:\n${this.friend.getMessage()}` : '.'}`;
  }

  friendlyType() { return 'Base User'; }

  static fromObject(value) {
    switch (value.type) {
      case 'happy':
        return plainToClass(HappyUser, value);
      case 'sad':
        return plainToClass(SadUser, value);
      default:
        return plainToClass(User, value);
    }
  }
}

class HappyUser extends User {
  friendlyType() { return 'Happy User'; }
}

class SadUser extends User {
  friendlyType() { return 'Sad User'; }
}

plainToClass(User, {
  name: 'A',
  friend: {
    name: 'A\'s Friend',
    type: 'happy',
  }
}).sayHi();

plainToClass(User, {
  name: 'B',
  friend: {
    name: 'B\'s Friend',
    type: 'sad',
  }
}).sayHi();

plainToClass(User, {
  name: 'C',
  friend: {
    name: 'C\'s Friend',
    type: 'unknown',
    friend: {
      name: '2nd Level Friend',
      type: 'happy',
      friend: {
        name: '3rd Level Friend',
        type: 'another',
      }
    }
  }
}).sayHi();

Thank you very much! @patricknazar

No worries @hedegitsinue hope it helps!

@patricknazar

Today I found a way to use this functionality and maybe you will find this helpful.

What I did: I wanted to transform JSON to object that has children of same kind ("Tag" class). But it was giving me "Tag_1" is not defined. Then I thought that transform function was changing the metadata definitions. I decided to take the class name outside of transform function and export it with a constant. After that it started to work properly. And also @Type() decorator started to work.

@Exclude()
export class Tag {

  @Expose({name: 'children_tags', toClassOnly: true})
  /*@Transform((childrenTags) => {
    if (childrenTags) {
      return childrenTags.map(child => plainToClass(TagClass, child));
    }
    return childrenTags;
  }, {toClassOnly: true})*/
  @Type(() => TagClass)
  children: Array<Tag> = [];

}

export const TagClass = Tag;

@hedegitsinue that's cool! However, apparently it is not working when using the discriminator option in @Type. I have multiple sub types and they all get resolved as undefined :/

If you still experience an issue with using discriminator with @Type decorator then please open a new issue and please
provide a __minimal__ reproducible example showcasing your problem. The example should:

  • be as short as possible why showcasing the problem
  • has no external dependencies used, only this lib
  • the expected and actual behavior should be explained

Closing as outdated.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lazarljubenovic picture lazarljubenovic  路  6Comments

ceopaludetto picture ceopaludetto  路  3Comments

prbaron picture prbaron  路  3Comments

taemini picture taemini  路  5Comments

braivre picture braivre  路  3Comments