I receive a JSON object from an AJAX call to a REST server. This object has property names that match my TypeScript class (this is a follow-on to this question).
Wha
My approach is slightly different. I do not copy properties into new instances, I just change the prototype of existing POJOs (may not work well on older browsers). Each class is responsible for providing a SetPrototypes method to set the prototoypes of any child objects, which in turn provide their own SetPrototypes methods.
(I also use a _Type property to get the class name of unknown objects but that can be ignored here)
class ParentClass
{
public ID?: Guid;
public Child?: ChildClass;
public ListOfChildren?: ChildClass[];
/**
* Set the prototypes of all objects in the graph.
* Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
* @param pojo Plain object received from API/JSON to be given the class prototype.
*/
private static SetPrototypes(pojo: ParentClass): void
{
ObjectUtils.SetPrototypeOf(pojo.Child, ChildClass);
ObjectUtils.SetPrototypeOfAll(pojo.ListOfChildren, ChildClass);
}
}
class ChildClass
{
public ID?: Guid;
public GrandChild?: GrandChildClass;
/**
* Set the prototypes of all objects in the graph.
* Used for recursive prototype assignment on a graph via ObjectUtils.SetPrototypeOf.
* @param pojo Plain object received from API/JSON to be given the class prototype.
*/
private static SetPrototypes(pojo: ChildClass): void
{
ObjectUtils.SetPrototypeOf(pojo.GrandChild, GrandChildClass);
}
}
Here is ObjectUtils.ts:
/**
* ClassType lets us specify arguments as class variables.
* (where ClassType == window[ClassName])
*/
type ClassType = { new(...args: any[]): any; };
/**
* The name of a class as opposed to the class itself.
* (where ClassType == window[ClassName])
*/
type ClassName = string & {};
abstract class ObjectUtils
{
/**
* Set the prototype of an object to the specified class.
*
* Does nothing if source or type are null.
* Throws an exception if type is not a known class type.
*
* If type has the SetPrototypes method then that is called on the source
* to perform recursive prototype assignment on an object graph.
*
* SetPrototypes is declared private on types because it should only be called
* by this method. It does not (and must not) set the prototype of the object
* itself - only the protoypes of child properties, otherwise it would cause a
* loop. Thus a public method would be misleading and not useful on its own.
*
* https://stackoverflow.com/questions/9959727/proto-vs-prototype-in-javascript
*/
public static SetPrototypeOf(source: any, type: ClassType | ClassName): any
{
let classType = (typeof type === "string") ? window[type] : type;
if (!source || !classType)
{
return source;
}
// Guard/contract utility
ExGuard.IsValid(classType.prototype, "type", type);
if ((Object).setPrototypeOf)
{
(Object).setPrototypeOf(source, classType.prototype);
}
else if (source.__proto__)
{
source.__proto__ = classType.prototype.__proto__;
}
if (typeof classType["SetPrototypes"] === "function")
{
classType["SetPrototypes"](source);
}
return source;
}
/**
* Set the prototype of a list of objects to the specified class.
*
* Throws an exception if type is not a known class type.
*/
public static SetPrototypeOfAll(source: any[], type: ClassType): void
{
if (!source)
{
return;
}
for (var i = 0; i < source.length; i++)
{
this.SetPrototypeOf(source[i], type);
}
}
}
Usage:
let pojo = SomePlainOldJavascriptObjectReceivedViaAjax;
let parentObject = ObjectUtils.SetPrototypeOf(pojo, ParentClass);
// parentObject is now a proper ParentClass instance