0

Using Angular & TypeScript, we can use generics and all the Compile-goodness to assure some sort of type-safety. But if we are using for example the HTTP-Service, we don't get a specific objec but just parsed JSON. For example, we have some generic methods doing that:

public get<T>(relativeUrl: string): Promise<T> {
    const completeUrlPromise = this.createCompleteUrl(relativeUrl);
    const requestOptions = this.createRequestOptions(ContentType.ApplicationJson, true);
    return completeUrlPromise.then(completeUrl => {
      return this.processResponse<T>(this.http.get(completeUrl, requestOptions));
    });
  }

  private processResponse<T>(response: Observable<Response>): Promise<T> {
    const mappedResult = response.map(this.extractData);

    const result = mappedResult.toPromise();
    return result;
  }

  private extractData(res: Response): any {
    let body;
    if (!Array.isArray(res)) {
      if (res.text()) {
        body = res.json();
      }
    } else {
      body = res;
    }

    if (!JsObjUtilities.isNullOrUndefined(body)) {
      return body;
    }

    return {};
  }

Ultimately, the generic type is useless this way, since we just get the JSON. If the generic object has methods or properties not in the JSON, they are lost. To avoid this, we added the possibility to pass a constructor-function to truly create the object:

 private processResponse<T>(response: Observable<Response>, ctor: IParameterlessConstructor<T> | null = null): Promise<T> {
    let mappedResult = response.map(this.extractData);

    if (ctor) {
      mappedResult = mappedResult.map(f => {
        const newObj = JsObjFactory.create(f, ctor);
        return newObj;
      });
    }

    const result = mappedResult.toPromise();
    return result;
  }

And the JsObjFactory looking like this:

export class JsObjFactory {
  public static create<T>(source: any, ctorFn: IParameterlessConstructor<T>): T {
    const result = new ctorFn();
    this.mapDefinedProperties(source, result);

    return result;
  }

  private static mapDefinedProperties<T>(source: Object, target: T): void {
    const properties = Object.getOwnPropertyNames(target);

    properties.forEach(propKey => {
      if (source.hasOwnProperty(propKey)) {
        target[propKey] = source[propKey];
      }
    });
  }
}

This works well for shallow objects, but doesn't work, if a property is also a complex type with a constructor. As there are no types at runtime, the best bet I have currently is to kindahow parse the properties, check if classes exist and then create them. But this seems to be very error-prone and cumbersome.

Since I'm always certain, I'm not the only person with this issues, are there solutions, or TypeScript/JavaScript features I'm not aware off, which would help here?

Matthias Müller
  • 3,336
  • 3
  • 33
  • 65
  • Possible duplicate of [How do I initialize a typescript object with a JSON object](https://stackoverflow.com/questions/22885995/how-do-i-initialize-a-typescript-object-with-a-json-object) (there are some good answers in there) – jcalz Sep 08 '17 at 13:28

1 Answers1

0

I don't personally do it like this, but it may be what you're looking for.

Example:

Customer.ts

export interface ICustomer {
    Id: number;
    Name: string;
    Orders: IOrder[];
    ...
}

export class Customer implements ICustomer {
    public Id: number;
    public Name: string;
    public Orders: IOrder[];

    constructor(customer: Partial<ICustomer>) {
        this.Id = customer.Id || 0;
        this.Name = customer.Name || '';
        this.Orders = [];
        customer.Orders.forEach((order: IOrder) => this.Orders.push(new Order(order)));
    }

    //some functions
}

Order.ts

export interface IOrder {
    Id: number;
    Weight: number;
    Shipmentdate: string;
}

export class Order implements IOrder {
    public Id: number;
    public Weight: number;
    public Shipmentdate: string;

    constructor(order: Partial<IOrder>) {
        this.Id = order.Id || 0;
        this.Weight = order.Weight || 0;
        this.Shipmentdate = order.Shipmentdate || '';
    }

    //some functions
}

This would make the Object (in this case Customer) responsible for instantiating it's known complex types that you pass in. And Order in turn could have its complex types that it instantiates.

Arg0n
  • 8,283
  • 2
  • 21
  • 38