How to convert typescript class to plain javascript object with class-transformer

Viewed 10098

I've some typescript classes instances in my angular app that I've to save to firebase. Firebase isn't supporting custom classes, so I found this: https://github.com/typestack/class-transformer that seems well fitted for me.

So now I'm creating my object, and trying to convert it.

Here is my object.

export class Trip {
  id: string;
  owner: string;
  name: string;
  @Type(() => Date)
  startDate: Date;
  @Type(() => Date)
  endDate: Date | undefined;
  coverImageUrl: string;

  @Type(() => Itinerary)
  itinerary: Itinerary;

  constructor() {
    this.itinerary = new Itinerary();
  }
}

export class Itinerary {
  @Type(() => ItineraryStats)
  stats: ItineraryStats;
  @Type(() => Step, {
    discriminator: {
      property: '__type',
      subTypes: [
        { value: ActivityStep, name: 'ActivityStep' },
        { value: NightStep, name: 'NightStep' },
        { value: PointOfInterest, name: 'PointOfInterest' },
        { value: TravelStep, name: 'TravelStep' },
      ],
    },
  })
  steps: Step[];

  constructor() {
    this.steps = Step[0];
    this.stats = new ItineraryStats();
  }
}

export class ItineraryStats {
  ///In days
  duration: number;
  //in hours
  driveTime: number;
  //in kilometers
  driveDistance: number;

  averageDriveDistancePerDay(): number {
    return this.driveDistance / this.duration;
  }
  averageDriveTimePerDay(): number {
    return this.driveTime / this.duration;
  }
}


export abstract class Step {
  @Type(() => Date)
  start: Date;

  @Type(() => Date)
  end: Date;
  duration: number;
  enforcedStartStop: boolean;
  warning: string;
}

export class ActivityStep extends StopStep {
  duration: number;

  constructor() {
    super();
    this.id = Guid.newGuid();
  }
}


export class NightStep extends StopStep {
  nightNumber: number;
  numberOfNight: number;
}

export class PointOfInterest {
  @Type(()=>PointOfInterest)
  type: PointOfInterest;
}


export class TravelStep extends Step {
  @Type(() => TravelMode)
  Mode: TravelMode;
  PolyLines: string;
}

I'm creating one "trip" and trying to convert it:

const trip = new Trip();
trip.owner= firebase.auth().currentUser.uid;
trip.name= action.name;
trip.startDate = action.startDate,
trip.endDate = action.endDate;
console.log(trip);
const converted = classToPlain(trip)

But I get this:

ERROR TypeError: Cannot read property 'constructor' of undefined
    at TransformOperationExecutor.js:207
    at Array.find (<anonymous>)
    at TransformOperationExecutor.transform (TransformOperationExecutor.js:207)
    at TransformOperationExecutor.transform (TransformOperationExecutor.js:266)
    at ClassTransformer.classToPlain (ClassTransformer.js:10)
    at classToPlain (index.js:8)
    at Object.toFirestore (trips.firestore.ts:16)
    at ma (index.cjs.js:15983)
    at n.set (index.cjs.js:15359)
    at AngularFirestoreDocument.set (angular-fire-firestore.js:512)

The object I just created: enter image description here

I guess either my object is in an improper state or I've something poorly defined, but I can't find what?

1 Answers

The error lies in the definition of Itinerary, more specifically the code line this.steps = Step[0]; which sets the variable to undefined and most likely wasn't intentional. The class-transformer library can deal with property steps being an empty array, array of steps, or not existing in the first place but crashes when the value is set to undefined.

export class Itinerary {
  @Type(() => ItineraryStats)
  stats: ItineraryStats;
  @Type(() => Step, {
    discriminator: {
      property: '__type',
      subTypes: [
        { value: ActivityStep, name: 'ActivityStep' },
        { value: NightStep, name: 'NightStep' },
        { value: PointOfInterest, name: 'PointOfInterest' },
        { value: TravelStep, name: 'TravelStep' },
      ],
    },
  })
  steps: Step[];

  constructor() {
    // Step[0] causes the crash, because its undefined, either dont set it or 
    // set it to this.steps = []
    this.steps = Step[0]; 
    this.stats = new ItineraryStats();
  }
}

More details: Due to the use of discriminators on property steps the corresponding logic kicked in and compared the constructors defined under subTypes, with the constructor of the current value to find the correct subtype name:

    if (this.transformationType === TransformationType.CLASS_TO_PLAIN) {
      subValue[targetType.options.discriminator.property] = targetType.options.discriminator.subTypes.find(
        subType => subType.value === subValue.constructor
      ).name;
    }

link to github

Step[0] being undefined eventually made this line subValue.constructor crash.

Related