-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
#167 has dirty attributes
- Loading branch information
Showing
15 changed files
with
3,356 additions
and
553 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { JsonModelConverter } from './json-model.converter'; | ||
import { School } from '../../../test/models/school.model'; | ||
|
||
describe('JsonModel converter', () => { | ||
let converter: JsonModelConverter<any>; | ||
|
||
describe('mask method', () => { | ||
|
||
describe('ArrayModel - simple values', () => { | ||
beforeEach(() => { | ||
converter = new JsonModelConverter(Array); | ||
}); | ||
|
||
it('should return empty array when empty input', () => { | ||
const result = converter.mask(null); | ||
expect(result instanceof Array).toBeTruthy(); | ||
}); | ||
|
||
it('should return the empty array when provided empty array', () => { | ||
const DATA: Array<any> = []; | ||
expect(converter.mask(DATA)).toEqual(DATA); | ||
}); | ||
|
||
it('should return the array with data when provided default value', () => { | ||
const DATA: Array<number> = [1, 2, 3]; | ||
expect(converter.mask(DATA)).toEqual(DATA); | ||
}); | ||
|
||
it('should filter-out null values from array', () => { | ||
const DATA: Array<number | null> = [1, 2, null, 3]; | ||
const result = converter.mask(DATA); | ||
expect(result.length).toBe(3); | ||
expect(result).not.toContain(null); | ||
}); | ||
}); | ||
|
||
describe('Array model -> object values', () => { | ||
|
||
beforeEach(() => { | ||
converter = new JsonModelConverter(School); | ||
}); | ||
|
||
it('should return array of Schools from provided data', () => { | ||
const DATA: Array<any> = [ | ||
{ name: 'Massachusetts Institute of Technology', students: 11319, foundation: '1861-10-04' }, | ||
{ name: 'Charles University', students: 51438, foundation: '1348-04-07' }, | ||
]; | ||
const result = converter.mask(DATA); | ||
expect(result.length).toBe(2); | ||
expect(result[0]).toEqual(new School(DATA[0])); | ||
expect(result[1]).toEqual(new School(DATA[1])); | ||
}); | ||
}); | ||
|
||
describe('ObjectModel', () => { | ||
beforeEach(() => { | ||
converter = new JsonModelConverter(School); | ||
}); | ||
|
||
it('should return new School when provided without value', () => { | ||
const result = converter.mask(null); | ||
expect(result instanceof School).toBeTruthy(); | ||
}); | ||
|
||
it('should not create new instance when already provided School instance', () => { | ||
const DATA = new School({ | ||
name: 'Massachusetts Institute of Technology', | ||
students: 11319, | ||
foundation: '1861-10-04' | ||
}); | ||
const result = converter.mask(DATA); | ||
expect(result).toEqual(DATA); | ||
}); | ||
|
||
it('should instance of School with data when provided initial data', () => { | ||
const DATA = { name: 'Massachusetts Institute of Technology', students: 11319, foundation: '1861-10-04' }; | ||
const result: School = converter.mask(DATA); | ||
expect(result.name).toBeTruthy(DATA.name); | ||
expect(result.students).toBeTruthy(DATA.students); | ||
expect(result.foundation).toBeTruthy(new Date(DATA.foundation)); | ||
}); | ||
}); | ||
|
||
describe('ObjectModel - nullable', () => { | ||
beforeEach(() => { | ||
converter = new JsonModelConverter(School, false); | ||
}); | ||
|
||
it('should return null when null', () => { | ||
const result = converter.mask(null); | ||
expect(result instanceof School).toBeTruthy(); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('unmask method', () => { | ||
describe('ArrayModel - simple values', () => { | ||
beforeEach(() => { | ||
converter = new JsonModelConverter(Array); | ||
}); | ||
|
||
it('should return serialized output when provided null', () => { | ||
const result = converter.unmask(null); | ||
expect(result).toBeNull(); | ||
}); | ||
|
||
it('should return serialized array of strings', () => { | ||
const DATA: Array<any> = ['a', 'b', 'c']; | ||
expect(converter.unmask(DATA)).toEqual(DATA); | ||
}); | ||
|
||
it('should filter-out null values from array', () => { | ||
const DATA: Array<number | null> = [1, 2, null, 3]; | ||
const result = converter.unmask(DATA); | ||
expect(result.length).toBe(3); | ||
expect(result).not.toContain(null); | ||
}); | ||
}); | ||
|
||
describe('Array model -> object values', () => { | ||
|
||
beforeEach(() => { | ||
converter = new JsonModelConverter(School); | ||
}); | ||
|
||
it('should return serialized Schools from provided Array of Schools', () => { | ||
const DATA: Array<any> = [ | ||
{ name: 'Massachusetts Institute of Technology', students: 11319, foundation: '1861-10-04' }, | ||
{ name: 'Charles University', students: 51438, foundation: '1348-04-07' }, | ||
]; | ||
const schools: Array<School> = [new School(DATA[0]), new School(DATA[1])]; | ||
const result:Array<any> = converter.unmask(schools); | ||
expect(result.length).toBe(2); | ||
for (const key in result) { | ||
expect(result[key].name).toBe(DATA[key].name); | ||
expect(result[key].students).toBe(DATA[key].students); | ||
expect(result[key].foundation).toContain(DATA[key].foundation); | ||
} | ||
}); | ||
}); | ||
|
||
describe('ObjectModel - nullable', () => { | ||
beforeEach(() => { | ||
converter = new JsonModelConverter(School, false); | ||
}); | ||
|
||
it('should return null when null', () => { | ||
const result = converter.unmask(null); | ||
expect(result).toEqual(null); | ||
}); | ||
|
||
it('should return serialized school when provided School instance', () => { | ||
const DATA = { name: 'Massachusetts Institute of Technology', students: 11319, foundation: '1861-10-04' }; | ||
const result = converter.unmask(new School(DATA)); | ||
expect(result.name).toBe(DATA.name); | ||
expect(result.students).toBe(DATA.students); | ||
expect(result.foundation).toContain(DATA.foundation); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { JsonNestedApiModel, PropertyConverter } from '../..'; | ||
|
||
export class JsonModelConverter<T> implements PropertyConverter { | ||
private modelType: any; // ModelType<T> | ||
|
||
constructor(model: T, public nullValue: boolean = true) { | ||
this.modelType = model; // <ModelType<T>>model | ||
} | ||
|
||
mask(value: any): T { | ||
if (!value && !this.nullValue) { | ||
return new this.modelType(); | ||
} | ||
|
||
let result = null; | ||
if (Array.isArray(value)) { | ||
result = []; | ||
for (const item of value) { | ||
if (item === null) { | ||
continue; | ||
} | ||
let temp; | ||
if (typeof item === 'object') { | ||
temp = new this.modelType(); | ||
temp.fill(item); | ||
} else { | ||
temp = item; | ||
} | ||
|
||
result.push(temp); | ||
} | ||
} else { | ||
if (!(value instanceof this.modelType)) { | ||
result = new this.modelType(); | ||
result.fill(value); | ||
} else { | ||
result = value; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
unmask(value: any): any { | ||
if (!value) { | ||
return value; | ||
} | ||
let result = null; | ||
if (Array.isArray(value)) { | ||
result = []; | ||
for (const item of value) { | ||
if (!item) { | ||
continue; | ||
} | ||
if (item instanceof JsonNestedApiModel) { | ||
item.nestedDataSerialization = true; | ||
result.push(item.serialize()); | ||
item.nestedDataSerialization = false; | ||
} else { | ||
result.push(item); | ||
} | ||
} | ||
} else { | ||
if (value instanceof JsonNestedApiModel) { | ||
value.nestedDataSerialization = true; | ||
result = value.serialize(); | ||
value.nestedDataSerialization = false; | ||
} else { | ||
result = value; | ||
} | ||
} | ||
return result; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { AttributeDecoratorOptions } from '../interfaces/attribute-decorator-options.interface'; | ||
import { DateConverter } from '../converters/date/date.converter'; | ||
import * as _ from 'lodash'; | ||
|
||
export function JsonAttribute(options: AttributeDecoratorOptions = {}): PropertyDecorator { | ||
return function (target: any, propertyName: string) { | ||
const converter = function (dataType: any, value: any, forSerialisation = false): any { | ||
let attrConverter; | ||
|
||
if (options.converter) { | ||
attrConverter = options.converter; | ||
} else if (dataType === Date) { | ||
attrConverter = new DateConverter(); | ||
} else { | ||
const datatype = new dataType(); | ||
|
||
if (datatype.mask && datatype.unmask) { | ||
attrConverter = datatype; | ||
} | ||
} | ||
|
||
if (attrConverter) { | ||
if (!forSerialisation) { | ||
return attrConverter.mask(value); | ||
} | ||
return attrConverter.unmask(value); | ||
} | ||
|
||
return value; | ||
}; | ||
|
||
const saveAnnotations = function () { | ||
const metadata = Reflect.getMetadata('JsonAttribute', target) || {}; | ||
|
||
metadata[propertyName] = { | ||
marked: true | ||
}; | ||
|
||
Reflect.defineMetadata('JsonAttribute', metadata, target); | ||
|
||
const mappingMetadata = Reflect.getMetadata('AttributeMapping', target) || {}; | ||
const serializedPropertyName = options.serializedName !== undefined ? options.serializedName : propertyName; | ||
mappingMetadata[serializedPropertyName] = propertyName; | ||
Reflect.defineMetadata('AttributeMapping', mappingMetadata, target); | ||
}; | ||
|
||
const getter = function () { | ||
if (this.nestedDataSerialization) { | ||
return converter(Reflect.getMetadata('design:type', target, propertyName), this['_' + propertyName], true); | ||
} | ||
return this['_' + propertyName]; | ||
}; | ||
|
||
const setter = function (newVal: any) { | ||
const targetType = Reflect.getMetadata('design:type', target, propertyName); | ||
this['_' + propertyName] = converter(targetType, newVal); | ||
}; | ||
|
||
if (delete target[propertyName]) { | ||
saveAnnotations(); | ||
Object.defineProperty(target, propertyName, { | ||
get: getter, | ||
set: setter, | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
} | ||
}; | ||
} |
Oops, something went wrong.