cerialize
Version:
Easy serialization throught ES7/Typescript annotations
428 lines (359 loc) • 13.3 kB
text/typescript
///<reference path="./typings/jasmine.d.ts"/>
import {
deserialize, deserializeAs, Deserialize, GenericDeserialize,
GenericDeserializeInto, deserializeIndexable
} from '../src/serialize';
class T1 {
public x : number;
public y : number;
}
class T2 {
public x : number;
public y : number;
public static OnDeserialized() : void {
}
}
class T3 {
(T2) child : T2;
x : number;
public static OnDeserialized() : void {
}
}
class T4 {
(Date) dateList : Array<Date>;
}
class T5 {
(Date) date: Date;
}
class JsonTest {
public obj : any;
constructor() {
this.obj = {
key1: 1,
nestedKey : {
key2: 2
}
}
}
}
class Fruit {
public name: string;
}
class Tree {
public value: string;
(Fruit) fruits: Array<Fruit>;
(Tree) trees: Array<Tree>;
}
var DeserializerFn = function(src : any) : any {
return 'custom!';
};
var Deserializer = {
Deserialize: DeserializerFn
};
class CustomDeserializeTest {
(Deserializer) public x : string;
}
class CustomDeserializeTest2 {
(DeserializerFn) public x : string;
}
describe('Deserialize', function () {
it('should not deserialize if not marked with deserializer', function () {
var json = { x: 1, y: 2 };
var instance = Deserialize(json, T1);
expect((instance instanceof T1)).toBe(true);
expect(instance.x).toBeUndefined();
expect(instance.y).toBeUndefined();
});
it('should deserialize if marked with deserializer', function () {
var json = { x: 1, y: 2 };
var instance = Deserialize(json, T2);
expect((instance instanceof T2)).toBe(true);
expect(instance.x).toBe(1);
expect(instance.y).toBe(2);
});
it('should deserialize an array', function () {
var json = [{ x: 1, y: 1 }, { x: 2, y: 2 }];
var list = Deserialize(json, T2);
expect(Array.isArray(list));
expect(list.length).toBe(2);
expect(list[0] instanceof T2).toBe(true);
expect(list[0].x).toBe(1);
expect(list[0].y).toBe(1);
expect(list[1] instanceof T2).toBe(true);
expect(list[1].x).toBe(2);
expect(list[1].y).toBe(2);
});
it('should deserialize a primitive', function () {
expect(Deserialize(1)).toBe(1);
expect(Deserialize(null)).toBe(null);
expect(Deserialize(false)).toBe(false);
expect(Deserialize(true)).toBe(true);
expect(Deserialize('1')).toBe('1');
});
it('should deserialize a date', function () {
var d = new Date();
var dateStr = d.toString();
var result = Deserialize(dateStr, Date);
expect(result instanceof Date).toBe(true);
});
it('should deserialize a date even when it\'s not a string', function () {
var d = new Date();
var result = Deserialize(d, Date);
expect(result instanceof Date).toBe(true);
expect(result.toString()).toEqual(d.toString())
});
it('should deserialize a regex', function () {
var r = /hi/;
var regexStr = r.toString();
var result = Deserialize(regexStr, RegExp);
expect(result instanceof RegExp).toBe(true);
});
it('should deserialize a nested object as a type', function () {
var t3 = { child: { x: 1, y: 1 }, x: 2 };
var result = Deserialize(t3, T3);
expect(result instanceof T3).toBe(true);
expect(result.child instanceof T2).toBe(true);
expect(result.child.x).toBe(1);
expect(result.child.y).toBe(1);
expect(result.x).toBe(2);
});
it('should deserialize a nested array as a type', function () {
var d1 = new Date();
var d2 = new Date();
var d3 = new Date();
var t4 = { dateList: [d1.toString(), d2.toString(), d3] };
var result = Deserialize(t4, T4);
expect(result instanceof T4).toBeTruthy();
expect(Array.isArray(result.dateList)).toBe(true);
expect(result.dateList[0].toString()).toEqual(d1.toString());
expect(result.dateList[1].toString()).toEqual(d2.toString());
expect(result.dateList[2].toString()).toEqual(d3.toString());
});
it('should deserialize a Date property even if source is a Date object', function () {
var t5 = { date: new Date() }
var result = Deserialize(t5, T5);
expect(result instanceof T5).toBeTruthy();
expect(result.date.toString()).toEqual(t5.date.toString());
});
it('should call OnDeserialize if defined on parent and or child', function () {
var json = {
child: {x : 1, y: 1},
x: 10
};
spyOn(T3, 'OnDeserialized').and.callThrough();
spyOn(T2, 'OnDeserialized').and.callThrough();
var result = Deserialize(json, T3);
expect(T3.OnDeserialized).toHaveBeenCalledWith(result, json);
expect(T2.OnDeserialized).toHaveBeenCalledWith(result.child, json.child);
});
it('should deserialize js objects tagged with deserialize', function(){
var testJson = new JsonTest();
var result = Deserialize(testJson, JsonTest);
expect(result).toBeDefined();
expect(typeof result.obj === "object").toBeTruthy();
expect(result.obj.key1).toBe(1);
expect(result.obj.nestedKey.key2).toBe(2);
});
it('should deserialize js primitive arrays tagged with deserialize', function() {
});
it('should use a custom deserializer', function() {
var testJson = {
"x": new Date().toString()
};
var result = Deserialize(testJson, CustomDeserializeTest);
expect(result.x).toBe("custom!");
});
it('should use a custom deserialize function', function() {
var testJson = {
"x": new Date().toString()
};
var result = Deserialize(testJson, CustomDeserializeTest2);
expect(result.x).toBe("custom!");
});
it('should cast to primitive array when given a primitive type', function() {
class Test {
(String) public arrayOfString: Array<string>;
(Number) public arrayOfNumber: Array<number>;
(Boolean) public arrayOfBoolean: Array<boolean>;
}
var json = {
arrayOfString: ['String1', 'String2'],
arrayOfNumber: [1, 2],
arrayOfBoolean: [true, false]
};
var test : Test = Deserialize(json, Test);
expect(Array.isArray(test.arrayOfString)).toBe(true);
expect(test.arrayOfString[0]).toBe("String1");
expect(test.arrayOfString[1]).toBe("String2");
expect(Array.isArray(test.arrayOfNumber)).toBe(true);
expect(test.arrayOfNumber[0]).toBe(1);
expect(test.arrayOfNumber[1]).toBe(2);
expect(Array.isArray(test.arrayOfBoolean)).toBe(true);
expect(test.arrayOfBoolean[0]).toBe(true);
expect(test.arrayOfBoolean[1]).toBe(false);
});
it('should cast to primitive type when given a primitive type', function() {
class Test {
(String) public str: string;
(Number) public num: number;
(Boolean) public bool: boolean;
(Number) public float: number;
}
var json = {
str: 1,
num: "2",
bool: 3,
float: "3.1415"
};
var test : Test = Deserialize(json, Test);
expect(test.str).toBe('1');
expect(test.num).toBe(2);
expect(test.bool).toBe(true);
expect(test.float).toBe(3.1415);
});
//contributed by @1ambda
it('should deserialize a json including nested empty arrays', function() {
var root1 = {
trees: new Array<Tree>(),
value: "root1"
};
var deserialized1 = Deserialize(root1, Tree);
expect(deserialized1.trees.length).toBe(0);
expect(deserialized1.value).toBe("root1");
/**
* `-- root
* |-- t1
* `-- t2
* |-- t3
* `-- t4
*/
var root2 = {
trees: [{
value: "t1" ,
trees: new Array<Tree>()
}, {
value: "t2",
trees: [{
value: "t3",
trees: new Array<Tree>()
}, {
value: "t4",
trees: new Array<Tree>()
}]
}],
value: "root2"
};
var deserialized2 = Deserialize(root2, Tree);
expect(deserialized2.trees.length).toBe(2);
expect(deserialized2.trees[0].trees.length).toBe(0); /* t1 includes empty trees */
expect(deserialized2.trees[1].trees.length).toBe(2); /* t2 includes 2 trees (t3, t4) */
});
it("should deserialize custom objects into an array", function() {
// class Item { }
})
it("should deserialize empty json into an empty string")
it('should deserialize a json including nested, multiple empty arrays', function() {
var root1 = {
fruits: new Array<Fruit>(),
trees: new Array<Tree>(),
value: "root1"
};
var deserialized1 = Deserialize(root1, Tree);
expect(deserialized1.trees.length).toBe(0);
expect(deserialized1.value).toBe("root1");
expect(deserialized1.fruits.length).toBe(0);
/**
* `-- root
* |-- t1 including f1
* `-- t2
* |-- t3 including f3
* `-- t4
*/
var root2 = {
trees: [{
value: "t1" ,
trees: new Array<Tree>(),
fruits: new Array<Fruit>(),
}, {
value: "t2",
trees: [{
value: "t3",
trees: new Array<Tree>(),
fruits: new Array<Fruit>(),
}, {
value: "t4",
trees: new Array<Tree>()
}]
}],
value: "root2"
};
var deserialized2 = Deserialize(root2, Tree);
expect(deserialized2.trees.length).toBe(2);
expect(deserialized2.trees[0].trees.length).toBe(0); /* t1 includes empty trees */
expect(deserialized2.trees[0].fruits.length).toBe(0); /* t1 includes empty fruits */
expect(deserialized2.trees[1].trees.length).toBe(2); /* t2 includes 2 trees (t3, t4) */
expect(deserialized2.trees[1].trees[0].trees.length).toBe(0); /* t3 includes empty trees */
expect(deserialized2.trees[1].trees[0].fruits.length).toBe(0); /* t3 includes fruits trees */
expect(deserialized2.trees[1].trees[1].trees.length).toBe(0); /* t4 includes empty trees */
expect(deserialized2.trees[1].trees[1].fruits).toBeUndefined(); /* t4 has no fruits */
});
it("Should deserialize indexable object", function () {
class Y {
thing : string;
}
class X {
(Y) yMap : any;
}
var map : any = {
yMap: {
1: { thing: '1' },
2: { thing: '2' }
}
};
var x : X = Deserialize(map, X);
expect(x.yMap[1] instanceof(Y)).toBe(true);
expect(x.yMap[2] instanceof(Y)).toBe(true);
});
});
describe('Deserialize generics', function () {
it('should handle a generic deserialize', function () {
var tree = GenericDeserialize({value: "someValue"}, Tree);
expect((tree instanceof Tree)).toBe(true);
expect(tree.value).toBe("someValue");
});
it('should handle a generic deserializeInto', function () {
var tree = new Tree();
tree.value = 'hello';
var tree2 = GenericDeserializeInto({value: "someValue"}, Tree, tree);
expect((tree2 instanceof Tree)).toBe(true);
expect(tree2).toBe(tree);
expect(tree.value).toBe("someValue");
});
});
//rest of file contributed by @1ambda
export interface NoParamConstructor<T> {
new (): T
}
export abstract class Deserializable {
public static deserialize<T>(ctor: NoParamConstructor<T>, json : any): T {
return Deserialize(json, ctor);
}
public static deserializeArray<T>(ctor: NoParamConstructor<T>, json : any): Array<T> {
return Deserialize(json, ctor);
}
}
class Car extends Deserializable {
public engine: string;
public wheels: number;
}
describe("Deserializable", () => {
describe("deserialize", () => {
it("should parse Car", () => {
let json : any = {engine: "M5", wheels: 4};
let c1 = Car.deserialize(Car, json);
let c2 = Car.deserialize<Car>(Car, json); // without NoParamConstructor
expect(c1.engine).toEqual(json.engine);
expect(c1.wheels).toEqual(json.wheels);
});
});
});