Skip to content

Commit

Permalink
Add test for initializer
Browse files Browse the repository at this point in the history
  • Loading branch information
Neos3452 committed Jul 28, 2019
1 parent 84b6707 commit 9a9462e
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 25 deletions.
4 changes: 2 additions & 2 deletions js/typedjson.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion js/typedjson.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/typedjson.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/typedjson.min.js.map

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions js/typedjson/json-object.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Constructor, ParameterlessConstructor } from "./types";
export declare type InitializerCallback<T> = (sourceObject: T, rawSourceObject: T) => T;
export interface IJsonObjectOptionsBase {
/**
* An array of known types to recognize when encountering type-hints,
Expand All @@ -17,22 +18,25 @@ export interface IJsonObjectOptionsBase {
}
export interface IJsonObjectOptionsWithInitializer<T> extends IJsonObjectOptionsBase {
/**
* The name of a static method to call before deserializing and initializing the object, accepting two arguments: (1) sourceObject, an 'Object' instance
* with all properties already deserialized, and (2) rawSourceObject, a raw 'Object' instance representation of the current object in the serialized JSON
* (i.e. without deserialized properties).
* Function to call before deserializing and initializing the object, accepting two arguments:
* (1) sourceObject, an 'Object' instance with all properties already deserialized, and
* (2) rawSourceObject, a raw 'Object' instance representation of the current object in
* the serialized JSON (i.e. without deserialized properties).
*/
initializer: (sourceObject: T, rawSourceObject: T) => T;
initializer: InitializerCallback<T>;
}
export interface IJsonObjectOptions<T> extends IJsonObjectOptionsBase {
/**
* The name of a static method to call before deserializing and initializing the object, accepting two arguments: (1) sourceObject, an 'Object' instance
* with all properties already deserialized, and (2) rawSourceObject, a raw 'Object' instance representation of the current object in the serialized JSON
* (i.e. without deserialized properties).
* Function to call before deserializing and initializing the object, accepting two arguments:
* (1) sourceObject, an 'Object' instance with all properties already deserialized, and
* (2) rawSourceObject, a raw 'Object' instance representation of the current object in
* the serialized JSON (i.e. without deserialized properties).
*/
initializer?: (sourceObject: T, rawSourceObject: T) => T;
initializer?: InitializerCallback<T>;
}
/**
* Marks that a class with a parameterized constructor is serializable using TypedJSON, with additional settings. The 'initializer' setting must be specified.
* Marks that a class with a parameterized constructor is serializable using TypedJSON, with additional
* settings. The 'initializer' setting must be specified.
* @param options Configuration settings.
*/
export declare function jsonObject<T>(options?: IJsonObjectOptionsWithInitializer<T>): (target: Constructor<T>) => void;
Expand Down
164 changes: 164 additions & 0 deletions spec/initializer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { jsonObject, jsonMember, TypedJSON } from "../js/typedjson";

describe('initializer', function () {

it('should be called', function() {
const initializerSpy = jasmine.createSpy().and.callFake((src, raw) => {
expect(src instanceof Person).toBeFalsy();
expect(src.getDescription).toBeUndefined();
expect(src.address instanceof Address).toBeTruthy();
expect(src.address.getAddressLine()).toEqual('44th, New York');

expect(raw instanceof Person).toBeFalsy();
expect(raw.getDescription).toBeUndefined();
expect(raw.address instanceof Address).toBeFalsy();
expect(raw.address.getAddressLine).toBeUndefined();

return new Person(src.name, src.address);
});

@jsonObject
class Address {
@jsonMember
street: string;
@jsonMember
city: string;

public getAddressLine() {
return `${this.street}, ${this.city}`;
}
}

@jsonObject({
initializer: initializerSpy,
})
class Person {
@jsonMember
name: string;

@jsonMember
address: Address;

constructor(name: string, address: Address) {
this.name = name;
this.address = address;
}

public getDescription() {
return `${this.name} is living at ${this.address.getAddressLine()}`;
}
}

const person = TypedJSON.parse(
{name: 'John', address: {street: '44th', city: 'New York'}},
Person
)!;
expect(person instanceof Person).toBeTruthy();
expect(person.getDescription()).toEqual('John is living at 44th, New York');
expect(initializerSpy).toHaveBeenCalled();
});

it('should fail if nothing is returned', function() {
const initializerSpy = jasmine.createSpy().and.callFake(() => null);

@jsonObject({
initializer: initializerSpy,
})
class Person {
@jsonMember
name: string;

public getDescription() {
return `${this.name} is his name`;
}
}

const errorHandlerSpy = jasmine.createSpy();
const person = TypedJSON.parse(
{name: 'John'},
Person,
{errorHandler: errorHandlerSpy},
)!;
expect(person).toBeUndefined();
expect(initializerSpy).toHaveBeenCalled();
expect(errorHandlerSpy).toHaveBeenCalled();
});

it('should fail if wrong type is returned', function() {
const initializerSpy = jasmine.createSpy()
.and.callFake((src) => new Person2(src.name));

@jsonObject({
initializer: initializerSpy,
})
class Person {
@jsonMember
name: string;

public getDescription() {
return `${this.name} is his name`;
}
}

class Person2 {
name: string;

constructor(name: string) {
this.name = name;
}

public getDescription() {
return `${this.name} is his name`;
}
}

const errorHandlerSpy = jasmine.createSpy();
const person = TypedJSON.parse(
{name: 'John'},
Person,
{errorHandler: errorHandlerSpy},
)!;
expect(person).toBeUndefined();
expect(initializerSpy).toHaveBeenCalled();
expect(errorHandlerSpy).toHaveBeenCalled();
});

it('should accept subtypes', function() {
const initializerSpy = jasmine.createSpy()
.and.callFake((src) => new Person2(src.name, 123));

@jsonObject({
initializer: initializerSpy,
})
class Person {
@jsonMember
name: string;

constructor(name: string) {
this.name = name;
}

public getDescription() {
return `${this.name} is his name`;
}
}

class Person2 extends Person{
age: number;

constructor(name: string, age: number) {
super(name);
this.age = age;
}

public getDescription() {
return `${super.getDescription()} and is ${this.age}y old`;
}
}

const person = TypedJSON.parse({name: 'John'}, Person)!;
expect(person instanceof Person2).toBeTruthy();
expect(person.getDescription()).toEqual('John is his name and is 123y old');
expect(initializerSpy).toHaveBeenCalled();
});
});
4 changes: 2 additions & 2 deletions src/typedjson/deserializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ export class Deserializer<T>
throw new TypeError(
`Cannot deserialize ${objectName}:`
+ `'initializer' returned '${nameof(targetObject.constructor)}'`
+ `, but '${nameof(sourceObjectMetadata.classType)}' was expected,`
+ `and '${nameof(targetObject.constructor)}' is not a subtype of`
+ `, but '${nameof(sourceObjectMetadata.classType)}' was expected`
+ `, and '${nameof(targetObject.constructor)}' is not a subtype of`
+ ` '${nameof(sourceObjectMetadata.classType)}'`,
);
}
Expand Down
23 changes: 14 additions & 9 deletions src/typedjson/json-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { METADATA_FIELD_KEY } from "./helpers";
import { JsonObjectMetadata } from "./metadata";

export type InitializerCallback<T> = (sourceObject: T, rawSourceObject: T) => T;

export interface IJsonObjectOptionsBase
{
/**
Expand All @@ -25,25 +27,28 @@ export interface IJsonObjectOptionsBase
export interface IJsonObjectOptionsWithInitializer<T> extends IJsonObjectOptionsBase
{
/**
* The name of a static method to call before deserializing and initializing the object, accepting two arguments: (1) sourceObject, an 'Object' instance
* with all properties already deserialized, and (2) rawSourceObject, a raw 'Object' instance representation of the current object in the serialized JSON
* (i.e. without deserialized properties).
* Function to call before deserializing and initializing the object, accepting two arguments:
* (1) sourceObject, an 'Object' instance with all properties already deserialized, and
* (2) rawSourceObject, a raw 'Object' instance representation of the current object in
* the serialized JSON (i.e. without deserialized properties).
*/
initializer: (sourceObject: T, rawSourceObject: T) => T;
initializer: InitializerCallback<T>;
}

export interface IJsonObjectOptions<T> extends IJsonObjectOptionsBase
{
/**
* The name of a static method to call before deserializing and initializing the object, accepting two arguments: (1) sourceObject, an 'Object' instance
* with all properties already deserialized, and (2) rawSourceObject, a raw 'Object' instance representation of the current object in the serialized JSON
* (i.e. without deserialized properties).
* Function to call before deserializing and initializing the object, accepting two arguments:
* (1) sourceObject, an 'Object' instance with all properties already deserialized, and
* (2) rawSourceObject, a raw 'Object' instance representation of the current object in
* the serialized JSON (i.e. without deserialized properties).
*/
initializer?: (sourceObject: T, rawSourceObject: T) => T;
initializer?: InitializerCallback<T>;
}

/**
* Marks that a class with a parameterized constructor is serializable using TypedJSON, with additional settings. The 'initializer' setting must be specified.
* Marks that a class with a parameterized constructor is serializable using TypedJSON, with additional
* settings. The 'initializer' setting must be specified.
* @param options Configuration settings.
*/
export function jsonObject<T>(options?: IJsonObjectOptionsWithInitializer<T>): (target: Constructor<T>) => void;
Expand Down

0 comments on commit 9a9462e

Please sign in to comment.