├── .npmignore ├── .travis.yml ├── src ├── index.ts ├── type │ ├── ClassOptions.ts │ ├── Serializable.ts │ └── PropertyOptions.ts ├── decorator │ ├── Serialize.ts │ └── SerializeProperty.ts └── serialize │ └── Serializer.ts ├── ts-serializer.ts ├── .gitignore ├── tsconfig.json ├── test ├── type │ └── Serializable.test.ts ├── decorator │ ├── Serialize.test.ts │ └── SerializeProperty.test.ts └── serialize │ └── Serializer.test.ts ├── webpack.config.js ├── package.json ├── README.md └── LICENSE /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | **/tsconfig.json 3 | **/webpack.config.js 4 | 5 | node_modules 6 | src -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | install: 5 | npm install 6 | script: 7 | - npm run test -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorator/Serialize'; 2 | export * from './decorator/SerializeProperty'; 3 | export * from './type/Serializable'; 4 | export * from './serialize/Serializer'; 5 | -------------------------------------------------------------------------------- /ts-serializer.ts: -------------------------------------------------------------------------------- 1 | export * from './src/decorator/Serialize'; 2 | export * from './src/decorator/SerializeProperty'; 3 | export * from './src/type/Serializable'; 4 | export * from './src/serialize/Serializer'; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | node_modules 4 | 5 | lib 6 | lib-esm 7 | _bundles 8 | dist 9 | .rts2_cache_umd 10 | .rts2_cache_es 11 | .rts2_cache_cjs 12 | coverage 13 | ts-serializer.iml 14 | .idea 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "outDir": "./dist/", 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "sourceMap": true, 8 | "noImplicitAny": true, 9 | "module": "commonjs", 10 | "target": "es5", 11 | "jsx": "react", 12 | "allowJs": true 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "test" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /test/type/Serializable.test.ts: -------------------------------------------------------------------------------- 1 | import { Serializable } from '../../src/type/Serializable'; 2 | 3 | describe('Serializable', () => { 4 | it('should throw error if serialize() and deserialize() methods are not overridden', () => { 5 | class Test extends Serializable {} 6 | 7 | const test = new Test(); 8 | 9 | expect(test.serialize).toThrowError( 10 | 'This is an abstract method. It needs to be overridden.' 11 | ); 12 | expect(test.deserialize).toThrowError( 13 | 'This is an abstract method. It needs to be overridden.' 14 | ); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: __dirname + '/ts-serializer', 5 | mode: 'production', 6 | devtool: 'inline-source-map', 7 | resolve: { 8 | extensions: [ '.tsx', '.ts', '.js' ] 9 | }, 10 | output:{ 11 | path: path.join(__dirname, 'dist'), 12 | publicPath: '/', 13 | filename: 'ts-serializer.js', 14 | libraryTarget: 'umd', 15 | library: 'TSerializer' 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.tsx?$/, 21 | use: 'ts-loader', 22 | exclude: /node_modules/ 23 | } 24 | ] 25 | } 26 | }; -------------------------------------------------------------------------------- /src/type/ClassOptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - Daniel Popescu 3 | */ 4 | /** 5 | * Defines the [[Serialize]] decorator options. 6 | */ 7 | export interface ClassOptions { 8 | /** 9 | * Root path to use when mapping. 10 | * 11 | * ## Example 12 | * ```JavaScript 13 | * @Serialize({ 14 | * root: 'someObject' 15 | * }) 16 | * class MyClass extends Serializable { 17 | * @SerializeProperty() 18 | * name:string; 19 | * } 20 | * ``` 21 | * ### Deserialize 22 | * ```JavaScript 23 | * let instance:MyClass = new MyClass(); 24 | * instance.deserialize({ 25 | * someObject: { 26 | * name: 'some value' 27 | * } 28 | * }); 29 | * 30 | * console.log(instance.name); // Will output 'some value' 31 | * ``` 32 | * ### Serialize 33 | * ```JavaScript 34 | * let instance:MyClass = new MyClass(); 35 | * instance.name = 'value'; 36 | * 37 | * console.log(instance.serialize()); // Will output {someObject:{name:'value'}} 38 | * ``` 39 | */ 40 | root?: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/type/Serializable.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - Daniel Popescu 3 | */ 4 | import { PropertyOptions } from './PropertyOptions'; 5 | /** 6 | * Helper class to represent serializable objects. The actual implementation of the [[Serializable.serialize]] and [[Serializable.deserialize]] 7 | * will be provided by the [[Serializer]] 8 | */ 9 | export abstract class Serializable { 10 | /** 11 | * Serialize as JSON Object 12 | */ 13 | serialize(): any { 14 | throw new Error('This is an abstract method. It needs to be overridden.'); 15 | } 16 | 17 | /** 18 | * Deserialize from JSON Object 19 | * @param jsonObject - The source object. 20 | */ 21 | // @ts-ignore 22 | deserialize(jsonObject: Record): void { 23 | throw new Error('This is an abstract method. It needs to be overridden.'); 24 | } 25 | 26 | /** 27 | * Keeps track of all decorated properties. 28 | * >**Note:** This property should only be used by the [[Serializer]] class 29 | * 30 | * @see [[SerializeProperty]] 31 | */ 32 | _serializeMap: Record = {}; 33 | 34 | /** 35 | * @hidden 36 | */ 37 | prototype: any; 38 | } 39 | -------------------------------------------------------------------------------- /test/decorator/Serialize.test.ts: -------------------------------------------------------------------------------- 1 | import { Serializable } from '../../src/type/Serializable'; 2 | import { Serialize } from '../../src/decorator/Serialize'; 3 | import { Serializer } from '../../src/serialize/Serializer'; 4 | 5 | describe('Serialize Decorator', () => { 6 | it('should add serialize and deserialize methods to the target class prototype', () => { 7 | @Serialize({}) 8 | class Test extends Serializable {} 9 | 10 | const test = new Test(); 11 | expect(test.serialize).toBeDefined(); 12 | expect(test.deserialize).toBeDefined(); 13 | }); 14 | 15 | it('should call Serializer.serialize with correct parameters', () => { 16 | spyOn(Serializer, 'serialize'); 17 | 18 | @Serialize({}) 19 | class Test extends Serializable {} 20 | 21 | const test = new Test(); 22 | test.serialize(); 23 | 24 | expect(Serializer.serialize).toHaveBeenCalledWith(Test, test, {}); 25 | }); 26 | 27 | it('should call Serializer.deserialize with correct parameters', () => { 28 | spyOn(Serializer, 'deserialize'); 29 | 30 | @Serialize({}) 31 | class Test extends Serializable {} 32 | 33 | const test = new Test(); 34 | test.deserialize({ 35 | test: 'test', 36 | }); 37 | 38 | expect(Serializer.deserialize).toHaveBeenCalledWith( 39 | Test, 40 | test, 41 | { 42 | test: 'test', 43 | }, 44 | {} 45 | ); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/decorator/Serialize.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - Daniel Popescu 3 | */ 4 | import { Serializer } from '../serialize/Serializer'; 5 | import { ClassOptions } from '../type/ClassOptions'; 6 | 7 | /** 8 | * # Serialize decorators 9 | * >**Note:** Can only be used on a class. 10 | * 11 | * ## How it works 12 | * This annotation will add the implementation for [[Serializable.serialize]] and [[Serializable.deserialize]] methods on the class prototype. 13 | * The current implementation will use the [[Serializer.serialize]] and [[Serializer.deserialize]] methods. 14 | * 15 | * ## Example 16 | * ### Simple decorator 17 | * ```JavaScript 18 | * @Serialize() 19 | * class MyClass extends Serializable {} 20 | * ``` 21 | * ### Decorator with options 22 | * ```JavaScript 23 | * @Serialize({ 24 | * root: 'someRootObject' 25 | * }) 26 | * class MyClass extends Serializable {} 27 | * ``` 28 | * @param classOptions - A set of options to use when decorating the class. 29 | * @returns {ClassDecorator} 30 | */ 31 | export function Serialize(classOptions: ClassOptions): ClassDecorator { 32 | return (target): void => { 33 | target.prototype.deserialize = function(jsonObject: object): void { 34 | Serializer.deserialize(target, this, jsonObject, classOptions); 35 | }; 36 | target.prototype.serialize = function(): object { 37 | return Serializer.serialize(target, this, classOptions); 38 | }; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/decorator/SerializeProperty.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - Daniel Popescu 3 | */ 4 | import { PropertyOptions } from '../type/PropertyOptions'; 5 | 6 | /** 7 | * # SerializeProperty decorator 8 | * >**Note:** Can only be used on class properties. 9 | * 10 | * ## How it works 11 | * This annotation creates a new `_serializeMap` property on the class prototype and adds all decorated properties to this map. 12 | * The [[Serializer]] will use this map to serialize and deserialize from/to json objects. 13 | * 14 | * ## Example 15 | * ### Simple decorator 16 | * ```JavaScript 17 | * @Serialize() 18 | * class MyClass extends Serializable { 19 | * @SerializeProperty() 20 | * simpleProperty:string; 21 | * } 22 | * ``` 23 | * ### Decorator with options 24 | * ```JavaScript 25 | * @Serialize() 26 | * class MyClass extends Serializable { 27 | * @SerializeProperty({ 28 | * map: 'someMapping', 29 | * root: 'someObject' 30 | * }) 31 | * simpleProperty:string; 32 | * } 33 | * ``` 34 | * @param options - A set of options to use when decorating a property. 35 | * @returns {PropertyDecorator} 36 | */ 37 | export function SerializeProperty( 38 | options: PropertyOptions = {} 39 | ): PropertyDecorator { 40 | return (target: object, name: string | symbol) => { 41 | if (!target.constructor.prototype._serializeMap) { 42 | target.constructor.prototype._serializeMap = {}; 43 | } 44 | options.name = name as string; 45 | target.constructor.prototype._serializeMap[name] = options; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-serializer", 3 | "version": "2.2.3", 4 | "repository": "https://github.com/dpopescu/ts-serializer", 5 | "author": { 6 | "name": "Daniel Popescu", 7 | "email": "danielpopescu95@gmail.com" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "Fellipe Pinheiro", 12 | "email": "pinheiro.llip@gmail.com" 13 | } 14 | ], 15 | "license": "Apache-2.0", 16 | "keywords": [ 17 | "typescript", 18 | "serialize", 19 | "deserialize", 20 | "library", 21 | "json", 22 | "mapping" 23 | ], 24 | "bugs": { 25 | "url": "https://github.com/dpopescu/ts-serializer/issues" 26 | }, 27 | "homepage": "https://github.com/dpopescu/ts-serializer#readme", 28 | "main": "dist/ts-serializer.js", 29 | "module": "dist/ts-serializer.js", 30 | "typings": "dist/ts-serializer.d.ts", 31 | "files": [ 32 | "dist" 33 | ], 34 | "scripts": { 35 | "build": "webpack", 36 | "test": "jest --verbose", 37 | "test-coverage": "jest --verbose --coverage", 38 | "publish": "npm publish --access=public" 39 | }, 40 | "peerDependencies": {}, 41 | "prettier": { 42 | "printWidth": 80, 43 | "semi": true, 44 | "singleQuote": true, 45 | "trailingComma": "es5" 46 | }, 47 | "devDependencies": { 48 | "@types/jest": "^24.0.13", 49 | "jest": "^24.8.0", 50 | "prettier": "^1.17.1", 51 | "ts-jest": "^24.0.2", 52 | "ts-loader": "^6.0.2", 53 | "typescript": "^3.4.5", 54 | "webpack": "^4.33.0", 55 | "webpack-cli": "^3.3.4" 56 | }, 57 | "jest": { 58 | "transform": { 59 | "^.+\\.tsx?$": "ts-jest" 60 | }, 61 | "globals": { 62 | "ts-jest": { 63 | "diagnostics": false 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/decorator/SerializeProperty.test.ts: -------------------------------------------------------------------------------- 1 | import { SerializeProperty } from '../../src/decorator/SerializeProperty'; 2 | import { Serializable } from '../../src/type/Serializable'; 3 | 4 | describe('SerializeProperty decorator', () => { 5 | it('should create an empty serializeMap', () => { 6 | class Test extends Serializable { 7 | @SerializeProperty() 8 | test: string; 9 | } 10 | 11 | expect(Test.prototype._serializeMap).toBeDefined(); 12 | }); 13 | it('should not overwrite the serializeMap if already exists', () => { 14 | class Test extends Serializable { 15 | @SerializeProperty() 16 | test: string; 17 | @SerializeProperty() 18 | anotherTest: string; 19 | } 20 | 21 | expect(Test.prototype._serializeMap).toEqual({ 22 | test: { 23 | name: 'test', 24 | }, 25 | anotherTest: { 26 | name: 'anotherTest', 27 | }, 28 | }); 29 | }); 30 | it('should add the correct options object to the serializeMap', () => { 31 | class Test extends Serializable { 32 | @SerializeProperty({ 33 | map: 'mapped_test', 34 | root: 'someObject', 35 | }) 36 | test: string; 37 | } 38 | 39 | expect(Test.prototype._serializeMap).toEqual({ 40 | test: { 41 | name: 'test', 42 | map: 'mapped_test', 43 | root: 'someObject', 44 | }, 45 | }); 46 | }); 47 | it('should add the correct optional option to the serializeMap', () => { 48 | class Test extends Serializable { 49 | @SerializeProperty({ 50 | map: 'mapped_test', 51 | optional: true, 52 | }) 53 | test: string; 54 | } 55 | 56 | expect(Test.prototype._serializeMap).toEqual({ 57 | test: { 58 | name: 'test', 59 | map: 'mapped_test', 60 | optional: true, 61 | }, 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/type/PropertyOptions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - Daniel Popescu 3 | */ 4 | import { ClassOptions } from './ClassOptions'; 5 | 6 | /** 7 | * Defines [[SerializeProperty]] decorator options 8 | */ 9 | export interface PropertyOptions extends ClassOptions { 10 | /** 11 | * Used by the [[Serializer]] to locate properties in the `_serializeMap`. This property should only be set by the [[SerializeProperty]] 12 | */ 13 | name?: string; 14 | /** 15 | * Used to map to a different property name in the json object. 16 | * 17 | * ## Example 18 | * ```JavaScript 19 | * @Serialize() 20 | * class MyClass extends Serializable { 21 | * @SerializeProperty({ 22 | * map: 'first_name' 23 | * }) 24 | * firstName:string; 25 | * } 26 | * ``` 27 | * ### Deserialize 28 | * ```JavaScript 29 | * let instance:MyClass = new MyClass(); 30 | * instance.deserialize({ 31 | * first_name: 'some value' 32 | * }); 33 | * 34 | * console.log(instance.firstName); // Will output 'some value' 35 | * ``` 36 | * ### Serialize 37 | * ```JavaScript 38 | * let instance:MyClass = new MyClass(); 39 | * instance.firstName = 'value' 40 | * 41 | * console.log(instance.serialize()); // Will output {first_name:'value'} 42 | * ``` 43 | */ 44 | map?: string; 45 | /** 46 | * Used to map a collection of elements. 47 | * 48 | * ## Example 49 | * ```JavaScript 50 | * @Serialize() 51 | * class MyClass extends Serializable { 52 | * @SerializeProperty({ 53 | * list: true 54 | * }) 55 | * values:string[]; 56 | * } 57 | * ``` 58 | * ### Deserialize 59 | * ```JavaScript 60 | * let instance:MyClass = new MyClass(); 61 | * instance.deserialize({ 62 | * values: ['a', 'b', 'c'] 63 | * }); 64 | * 65 | * console.log(instance.values); // Will output ['a', 'b', 'c'] 66 | * ``` 67 | * ### Serialize 68 | * ```JavaScript 69 | * let instance:MyClass = new MyClass(); 70 | * instance.values = ['a', 'b', 'c']; 71 | * 72 | * console.log(instance.serialize()); // Will output {values:['a','b','c']} 73 | * ``` 74 | */ 75 | list?: boolean; 76 | /** 77 | * Specifies the type of the property. 78 | * 79 | * ## Example 80 | * ```JavaScript 81 | * @Serialize() 82 | * class User extends Serializable { 83 | * @SerializeProperty() 84 | * firstName:string; 85 | * @SerializeProperty() 86 | * lastName:string; 87 | * } 88 | * 89 | * @Serialize() 90 | * class Profile extends Serializable { 91 | * @SerializeProperty({ 92 | * type: User 93 | * }) 94 | * user:User; 95 | * } 96 | * ``` 97 | * ### Deserialize 98 | * ```JavaScript 99 | * let profile:Profile = new Profile(); 100 | * profile.deserialize({ 101 | * user: { 102 | * firstName: 'John', 103 | * lastName: 'Doe' 104 | * } 105 | * }); 106 | * 107 | * console.log(profile.user.firstName); // Will output 'John' 108 | * console.log(profile.user.lastName); // Will output 'Doe' 109 | * ``` 110 | * ### Serialize 111 | * ```JavaScript 112 | * let profile:Profile = new Profile(); 113 | * profile.user = new User(); 114 | * profile.user.firstName = 'John'; 115 | * profile.user.lastName = 'Doe'; 116 | * 117 | * console.log(profile.serialize()); // Will output {user:{firstName:'John', lastName:'Doe'}} 118 | * ``` 119 | */ 120 | type?: any; 121 | /** 122 | * Specifies if the property is optional or not. 123 | * 124 | * ## Example 125 | * ```JavaScript 126 | * @Serialize() 127 | * class User extends Serializable { 128 | * @SerializeProperty() 129 | * name:string; 130 | * @SerializeProperty({ 131 | * optional: true 132 | * }) 133 | * age:number; 134 | * } 135 | * ``` 136 | * ### Deserialize 137 | * ```JavaScript 138 | * let user:User = new User(); 139 | * user.deserialize({ 140 | * name: 'John' 141 | * }); 142 | * 143 | * console.log(profile.name); // Will output 'John' 144 | * console.log(profile.age); // Will output 'null' 145 | * ``` 146 | * ### Serialize 147 | * ```JavaScript 148 | * let user:User = new User(); 149 | * user.name = 'John'; 150 | * 151 | * console.log(user.serialize()); // Will output {name:'John', age:'null} 152 | * ``` 153 | */ 154 | optional?: boolean; 155 | } 156 | -------------------------------------------------------------------------------- /src/serialize/Serializer.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - Daniel Popescu 3 | */ 4 | import { Serializable } from '../type/Serializable'; 5 | import { ClassOptions } from '../type/ClassOptions'; 6 | import { PropertyOptions } from '../type/PropertyOptions'; 7 | 8 | /** 9 | * Utility class to serialize and deserialize objects 10 | */ 11 | export class Serializer { 12 | /** 13 | * Deserialize a property based on it's type. 14 | * @see [[SerializeProperty]], [[PropertyOptions.type]] 15 | * @param options - A set of options to use when deserializing this property. 16 | * @param value - The object to deserialize. 17 | * @returns {any} - The deserialized object. 18 | */ 19 | private static deserializeItem(options: PropertyOptions, value: object) { 20 | if (options.optional && !isValidValue(value)) { 21 | return null; 22 | } else if (options.type) { 23 | const item = new options.type(); 24 | item.deserialize(value); 25 | return item; 26 | } else { 27 | return value; 28 | } 29 | } 30 | 31 | /** 32 | * Serialize a property based on it's type. 33 | * @see [[SerializeProperty]], [[PropertyOptions.type]] 34 | * @param options - A set of options to use when serializing this property. 35 | * @param value - The object to serialize. 36 | * @returns {Object | null} - The serialized object. 37 | */ 38 | private static serializeItem(options: PropertyOptions, value: any): object | null { 39 | if (options.optional && !isValidValue(value)) { 40 | return null; 41 | } else if (options.type) { 42 | return (value as Serializable).serialize(); 43 | } else { 44 | return value; 45 | } 46 | } 47 | 48 | /** 49 | * Serialize a class instance. 50 | * @see [[Serialize]], [[ClassOptions]] 51 | * @param target - Class type. 52 | * @param context - Instance to serialize. 53 | * @param classOptions - Class serialization options. 54 | * @returns {object} - The serialized object. 55 | */ 56 | public static serialize( 57 | target: any, 58 | context: Record, 59 | classOptions: ClassOptions 60 | ): object { 61 | const result: Record = {}; 62 | 63 | Object.keys(target.prototype._serializeMap).forEach(name => { 64 | const value = validateFalseOrZero(context[name]); 65 | const options = target.prototype._serializeMap[name]; 66 | const rootPath = options.root || classOptions.root || null; 67 | const mapName = options.map || options.name; 68 | let dataTarget = result; 69 | 70 | if (rootPath && rootPath !== '.') { 71 | if (!result[rootPath]) { 72 | result[rootPath] = {}; 73 | } 74 | dataTarget = result[rootPath]; 75 | } 76 | 77 | if (options.list) { 78 | if (value) { 79 | dataTarget[mapName] = value.map((v: any) => this.serializeItem(options, v)); 80 | } else { 81 | dataTarget[mapName] = []; 82 | } 83 | } else { 84 | dataTarget[mapName] = this.serializeItem(options, value); 85 | } 86 | }); 87 | 88 | return result; 89 | } 90 | 91 | /** 92 | * Deserialize a class instance. 93 | * @see [[Serialize]], [[ClassOptions]] 94 | * @param target - Class type. 95 | * @param context - Instance to deserialize. 96 | * @param jsonObject - Object to deserialize. 97 | * @param classOptions - Class deserialization options. 98 | */ 99 | public static deserialize( 100 | target: any, 101 | context: Record, 102 | jsonObject: Record, 103 | classOptions: ClassOptions = {} 104 | ) { 105 | Object.keys(target.prototype._serializeMap).forEach(name => { 106 | const options: PropertyOptions = target.prototype._serializeMap[name]; 107 | const rootPath: string | null = options.root || classOptions.root || null; 108 | const mapName: string = options.map || options.name || ''; 109 | let value = validateFalseOrZero(jsonObject[mapName]); 110 | 111 | if (rootPath && rootPath !== '.' && valueIsDefined(jsonObject[rootPath])) { 112 | value = jsonObject[rootPath][mapName]; 113 | } 114 | 115 | if (options.list) { 116 | if (value) { 117 | context[name] = value.map((v: any) => this.deserializeItem(options, v)); 118 | } else { 119 | context[name] = []; 120 | } 121 | } else { 122 | context[name] = this.deserializeItem(options, value); 123 | } 124 | }); 125 | } 126 | } 127 | 128 | function valueIsDefined(value: any): boolean { 129 | return value !== undefined && value !== null 130 | } 131 | 132 | function validateFalseOrZero(value: any): any { 133 | return value ? value : value === false || value === 0 ? value : null; 134 | } 135 | 136 | function isValidValue(value: any): boolean { 137 | return value ? true : value === false || value === 0 ? true : false; 138 | } 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

I'm a contributor from the original project now, please, use dpopescu instead.

2 | 3 | 4 |

TS Serializer

5 | 6 |

7 | 8 | npm 9 | 10 | 11 | license 12 | 13 | 14 | GitHub stars 15 | 16 |

17 | 18 |
19 | 20 | > **TS Serializer** provides `TypeScript` decorators to help developers with serializing/deserializing TypeScript classes into and from JSON objects. 21 | 22 | ## Installation 23 | 24 | ### Using npm: 25 | 26 | `npm install --save ts-serializer` 27 | 28 | ## Usage 29 | 30 | You can find a [codesandbox playground here](https://codesandbox.io/s/ts-serializer-playground-gdbid)! 31 | 32 | ### TypeScript 33 | 34 | #### Deserialization: 35 | 36 | ```TypeScript 37 | import {Serialize, SerializeProperty, Serializable} from 'ts-serializer'; 38 | 39 | @Serialize({}) 40 | class MyClass extends Serializable { 41 | @SerializeProperty({ 42 | map: 'username' 43 | }) 44 | public name:string; 45 | } 46 | 47 | let instance:MyClass = new MyClass(); 48 | 49 | console.log(instance.name); // Prints `undefined` 50 | 51 | instance.deserialize({ 52 | username: 'Some Value' 53 | }); 54 | 55 | console.log(instance.name); // Prints 'Some Value' 56 | ``` 57 | 58 | --- 59 | 60 | #### Serialization: 61 | 62 | ```TypeScript 63 | import {Serialize, SerializeProperty, Serializable} from 'ts-serializer'; 64 | 65 | @Serialize({}) 66 | class MyClass extends Serializable { 67 | @SerializeProperty({ 68 | map: 'username' 69 | }) 70 | public name:string; 71 | } 72 | 73 | let instance:MyClass = new MyClass(); 74 | instance.name = 'Some Value'; 75 | 76 | console.log(instance.serialize()); // Prints {username:'Some Value'} 77 | ``` 78 | 79 | ### JavaScript 80 | 81 | > **Note:** Although the library was designed to be used as a decorator in TypeScript, it doesn't matter that it can't be used in plain old JavaScript. The syntax can be a little messy but the result is the same. 82 | 83 | #### Deserialization: 84 | 85 | ```JavaScript 86 | var Serialize = TSerializer.Serialize; 87 | var SerializeProperty = TSerializer.SerializeProperty; 88 | 89 | function MyClass(){ 90 | this.name; 91 | } 92 | 93 | Serialize({})(MyClass); 94 | SerializeProperty({ 95 | map: 'username' 96 | })(MyClass.prototype, 'name'); 97 | 98 | var instance = new MyClass(); 99 | 100 | console.log(instance.name); // Prints `undefined` 101 | 102 | instance.deserialize({ 103 | username: 'Some Value' 104 | }); 105 | 106 | console.log(instance.name); // Prints 'Some Value' 107 | ``` 108 | 109 | --- 110 | 111 | #### Serialization: 112 | 113 | ```JavaScript 114 | var Serialize = TSerializer.Serialize; 115 | var SerializeProperty = TSerializer.SerializeProperty; 116 | 117 | function MyClass(){ 118 | this.name = 'Some Value'; 119 | } 120 | 121 | Serialize({})(MyClass); 122 | SerializeProperty({ 123 | map: 'username' 124 | })(MyClass.prototype, 'name'); 125 | 126 | var instance = new MyClass(); 127 | 128 | console.log(instance.serialize()); // Prints {username:'Some Value'} 129 | ``` 130 | 131 | ## Library Options 132 | 133 | The library allows you to pass different serialization/deserialization options both on class level and on property level. 134 | 135 | ### Class Options 136 | 137 | #### root 138 | 139 | When you want to deserialize just a child object from the JSON you can use the `root` option. 140 | 141 | ```TypeScript 142 | @Serialize({ 143 | root: 'childObject' 144 | }) 145 | class MyClass extends Serializable { 146 | @SerializeProperty({}) 147 | public name:string; 148 | } 149 | 150 | let instance:MyClass = new MyClass(); 151 | instance.deserialize({ 152 | childObject: { 153 | name: 'Some Value' 154 | } 155 | }); 156 | 157 | console.log(instance.name); // Prints 'Some Value' 158 | ``` 159 | 160 | ### Property Options 161 | 162 | #### root 163 | 164 | The `root` option can also be used on a property. 165 | 166 | > **Note:** If `root` is already specified at class level the value is inherited to all class properties. If you want to override this, you can use hte `.` value. In this case, the property will be mapped up one level. 167 | 168 | ```TypeScript 169 | @Serialize({}) 170 | class MyClass extends Serializable { 171 | @SerializeProperty({ 172 | root: 'childObject' 173 | }) 174 | public name:string; 175 | } 176 | 177 | let instance:MyClass = new MyClass(); 178 | instance.deserialize({ 179 | childObject: { 180 | name: 'Some Value' 181 | } 182 | }); 183 | 184 | console.log(instance.name); // Prints 'Some Value' 185 | ``` 186 | 187 | --- 188 | 189 | #### map 190 | 191 | When the property name in the JSON doesn't match with your class properties, the `map` option can be used. This option maps a property from the JSON with a different property from your class. 192 | 193 | ```TypeScript 194 | @Serialize({}) 195 | class MyClass extends Serializable { 196 | @SerializeProperty({ 197 | map: 'username' 198 | }) 199 | public name:string; 200 | } 201 | 202 | let instance:MyClass = new MyClass(); 203 | instance.deserialize({ 204 | username: 'Some Value' 205 | }); 206 | 207 | console.log(instance.name); // Prints 'Some Value' 208 | ``` 209 | 210 | --- 211 | 212 | #### list 213 | 214 | The `list` option can be used when the JSON property value is a list of items. 215 | 216 | ```TypeScript 217 | @Serialize({}) 218 | class MyClass extends Serializable { 219 | @SerializeProperty({ 220 | list: true 221 | }) 222 | public items:string[]; 223 | } 224 | 225 | let instance:MyClass = new MyClass(); 226 | instance.deserialize({ 227 | items: ['a', 'b', 'c'] 228 | }); 229 | 230 | console.log(instance.items); // Prints ['a', 'b', 'c'] 231 | ``` 232 | 233 | --- 234 | 235 | #### type 236 | 237 | When you want to use non-primitive types for deserialization use the `type` option. 238 | 239 | > **Note:** The `type` object should also be a `Serializable` object. 240 | 241 | ```TypeScript 242 | @Serialize({}) 243 | class User extends Serializable { 244 | @SerializeProperty({}) 245 | public firstName:string; 246 | @SerializeProperty({}) 247 | public lastName:string; 248 | } 249 | 250 | @Serialize({}) 251 | class Profile extends Serializable { 252 | @SerializeProperty({ 253 | type: User 254 | }) 255 | public user:User; 256 | } 257 | 258 | let instance:Profile = new Profile(); 259 | instance.deserialize({ 260 | user: { 261 | firstName: 'John', 262 | lastName: 'Doe' 263 | } 264 | }); 265 | 266 | console.log(instance.user.firstName); // Prints 'John' 267 | console.log(instance.user.lastName); // Prints 'Doe' 268 | ``` 269 | 270 | #### optional 271 | 272 | The `optional` option can be used when the property or the value may not exist. 273 | 274 | ```TypeScript 275 | @Serialize({}) 276 | class User extends Serializable { 277 | @SerializeProperty({}) 278 | public name:string; 279 | @SerializeProperty({ 280 | optional: true 281 | }) 282 | public age:number; 283 | } 284 | 285 | @Serialize({}) 286 | class Profile extends Serializable { 287 | @SerializeProperty({ 288 | type: User 289 | }) 290 | public user:User; 291 | } 292 | 293 | let instance:Profile = new Profile(); 294 | instance.deserialize({ 295 | user: { 296 | firstName: 'John', 297 | } 298 | }); 299 | 300 | console.log(instance.user.firstName); // Prints 'John' 301 | console.log(instance.user.age); // Prints 'null' 302 | ``` 303 | 304 | # Contribute 305 | 306 | You can help improving this project sending PRs and helping with issues. 307 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/serialize/Serializer.test.ts: -------------------------------------------------------------------------------- 1 | import {Serializer} from '../../src/serialize/Serializer'; 2 | 3 | describe('Serializer', () => { 4 | it('should define methods for serialization and deserialization', () => { 5 | expect(Serializer.serialize).toBeDefined(); 6 | expect(Serializer.deserialize).toBeDefined(); 7 | }); 8 | 9 | describe('serialize()', () => { 10 | it("should return an empty object if target's _serializeMap is empty", () => { 11 | const value = Serializer.serialize( 12 | { 13 | prototype: { 14 | _serializeMap: {}, 15 | }, 16 | }, 17 | {}, 18 | {} 19 | ); 20 | 21 | expect(value).toEqual({}); 22 | }); 23 | it('should return a json object for mapped properties', () => { 24 | const value = Serializer.serialize( 25 | { 26 | prototype: { 27 | _serializeMap: { 28 | a: {name: 'a'}, 29 | b: {name: 'b'}, 30 | c: {name: 'c'}, 31 | }, 32 | }, 33 | }, 34 | { 35 | a: 'a value', 36 | b: 'b value', 37 | c: 'c value', 38 | }, 39 | {} 40 | ); 41 | 42 | expect(value).toEqual({ 43 | a: 'a value', 44 | b: 'b value', 45 | c: 'c value', 46 | }); 47 | }); 48 | it('should return a json object for properties with map option', () => { 49 | const value = Serializer.serialize( 50 | { 51 | prototype: { 52 | _serializeMap: { 53 | a: {name: 'a'}, 54 | b: {name: 'b', map: 'mapped_b'}, 55 | c: {name: 'c'}, 56 | }, 57 | }, 58 | }, 59 | { 60 | a: 'a value', 61 | b: 'b value', 62 | c: 'c value', 63 | }, 64 | {} 65 | ); 66 | 67 | expect(value).toEqual({ 68 | a: 'a value', 69 | mapped_b: 'b value', 70 | c: 'c value', 71 | }); 72 | }); 73 | it('should return a json object for properties with root option', () => { 74 | const value = Serializer.serialize( 75 | { 76 | prototype: { 77 | _serializeMap: { 78 | a: {name: 'a'}, 79 | b: {name: 'b', root: 'someObject'}, 80 | c: {name: 'c'}, 81 | }, 82 | }, 83 | }, 84 | { 85 | a: 'a value', 86 | b: 'b value', 87 | c: 'c value', 88 | }, 89 | {} 90 | ); 91 | 92 | expect(value).toEqual({ 93 | a: 'a value', 94 | someObject: { 95 | b: 'b value', 96 | }, 97 | c: 'c value', 98 | }); 99 | }); 100 | it('should return a json object for properties with class level root option', () => { 101 | const value = Serializer.serialize( 102 | { 103 | prototype: { 104 | _serializeMap: { 105 | a: {name: 'a'}, 106 | b: {name: 'b'}, 107 | c: {name: 'c'}, 108 | }, 109 | }, 110 | }, 111 | { 112 | a: 'a value', 113 | b: 'b value', 114 | c: 'c value', 115 | }, 116 | {root: 'someObject'} 117 | ); 118 | 119 | expect(value).toEqual({ 120 | someObject: { 121 | a: 'a value', 122 | b: 'b value', 123 | c: 'c value', 124 | }, 125 | }); 126 | }); 127 | it('should return a json object for properties with list option', () => { 128 | const value = Serializer.serialize( 129 | { 130 | prototype: { 131 | _serializeMap: { 132 | a: {name: 'a'}, 133 | b: {name: 'b', list: true}, 134 | c: {name: 'c'}, 135 | }, 136 | }, 137 | }, 138 | { 139 | a: 'a value', 140 | b: ['a', 'b', 'c'], 141 | c: 'c value', 142 | }, 143 | {} 144 | ); 145 | 146 | expect(value).toEqual({ 147 | a: 'a value', 148 | b: ['a', 'b', 'c'], 149 | c: 'c value', 150 | }); 151 | }); 152 | it('should return a json object for properties with type option', () => { 153 | const test: any = function Test(): void { 154 | this.serialize = () => { 155 | return { 156 | test: 'test', 157 | }; 158 | }; 159 | }; 160 | 161 | const value = Serializer.serialize( 162 | { 163 | prototype: { 164 | _serializeMap: { 165 | a: {name: 'a'}, 166 | b: {name: 'b', type: test}, 167 | c: {name: 'c'}, 168 | }, 169 | }, 170 | }, 171 | { 172 | a: 'a value', 173 | b: new test(), 174 | c: 'c value', 175 | }, 176 | {} 177 | ); 178 | 179 | expect(value).toEqual({ 180 | a: 'a value', 181 | b: {test: 'test'}, 182 | c: 'c value', 183 | }); 184 | }); 185 | it('should return a json object for properties with both list and type options', () => { 186 | const test: any = function Test(): void { 187 | this.serialize = () => { 188 | return { 189 | test: 'test', 190 | }; 191 | }; 192 | }; 193 | 194 | const value = Serializer.serialize( 195 | { 196 | prototype: { 197 | _serializeMap: { 198 | a: {name: 'a'}, 199 | b: {name: 'b', list: true, type: test}, 200 | c: {name: 'c'}, 201 | }, 202 | }, 203 | }, 204 | { 205 | a: 'a value', 206 | b: [new test(), new test(), new test()], 207 | c: 'c value', 208 | }, 209 | {} 210 | ); 211 | 212 | expect(value).toEqual({ 213 | a: 'a value', 214 | b: [{test: 'test'}, {test: 'test'}, {test: 'test'}], 215 | c: 'c value', 216 | }); 217 | }); 218 | it('should return a json object for properties with both list and type options', () => { 219 | const test: any = function Test(): void { 220 | this.serialize = (v: any) => { 221 | return { 222 | test: v.test, 223 | }; 224 | }; 225 | }; 226 | 227 | const value = Serializer.serialize( 228 | { 229 | prototype: { 230 | _serializeMap: { 231 | a: {name: 'a'}, 232 | b: {name: 'b', type: test, optional: true}, 233 | c: {name: 'c', optional: true}, 234 | d: {name: 'd', list: true, optional: true}, 235 | }, 236 | }, 237 | }, 238 | { 239 | a: 'a value', 240 | }, 241 | {} 242 | ); 243 | 244 | expect(value).toEqual({ 245 | a: 'a value', 246 | b: null, 247 | c: null, 248 | d: [], 249 | }); 250 | }); 251 | it('should return a json object with the right negative values', () => { 252 | const value = Serializer.serialize( 253 | { 254 | prototype: { 255 | _serializeMap: { 256 | a: {name: 'a'}, 257 | b: {name: 'b'}, 258 | c: {name: 'c'}, 259 | d: {name: 'd'}, 260 | e: {name: 'e'}, 261 | f: {name: 'f'}, 262 | }, 263 | }, 264 | }, 265 | { 266 | a: true, 267 | b: false, 268 | c: null, 269 | d: undefined, 270 | e: 0, 271 | f: 1, 272 | }, 273 | {} 274 | ); 275 | 276 | expect(value).toEqual({ 277 | a: true, 278 | b: false, 279 | c: null, 280 | d: null, 281 | e: 0, 282 | f: 1, 283 | }); 284 | }); 285 | }); 286 | }); 287 | describe('deserialize()', () => { 288 | it('should leave the context unchanged if the _serializeMap is empty', () => { 289 | const context = {}; 290 | 291 | Serializer.deserialize( 292 | { 293 | prototype: { 294 | _serializeMap: {}, 295 | }, 296 | }, 297 | context, 298 | { 299 | a: 'a', 300 | b: 'b', 301 | c: 'c', 302 | } 303 | ); 304 | 305 | expect(context).toEqual({}); 306 | }); 307 | it('should map a simple json object to an existing context', () => { 308 | const context = {}; 309 | 310 | Serializer.deserialize( 311 | { 312 | prototype: { 313 | _serializeMap: { 314 | a: {name: 'a'}, 315 | b: {name: 'b'}, 316 | c: {name: 'c'}, 317 | }, 318 | }, 319 | }, 320 | context, 321 | { 322 | a: 'a', 323 | b: 'b', 324 | c: 'c', 325 | } 326 | ); 327 | 328 | expect(context).toEqual({ 329 | a: 'a', 330 | b: 'b', 331 | c: 'c', 332 | }); 333 | }); 334 | it('should map a json object to a context with class level root option', () => { 335 | const context = {}; 336 | 337 | Serializer.deserialize( 338 | { 339 | prototype: { 340 | _serializeMap: { 341 | a: {name: 'a'}, 342 | b: {name: 'b'}, 343 | c: {name: 'c'}, 344 | }, 345 | }, 346 | }, 347 | context, 348 | { 349 | someObject: { 350 | a: 'a', 351 | b: 'b', 352 | c: 'c', 353 | }, 354 | }, 355 | {root: 'someObject'} 356 | ); 357 | 358 | expect(context).toEqual({ 359 | a: 'a', 360 | b: 'b', 361 | c: 'c', 362 | }); 363 | }); 364 | it('should map a json object to a context property with root option', () => { 365 | const context = {}; 366 | 367 | Serializer.deserialize( 368 | { 369 | prototype: { 370 | _serializeMap: { 371 | a: {name: 'a'}, 372 | b: {name: 'b', root: 'someObject'}, 373 | c: {name: 'c'}, 374 | }, 375 | }, 376 | }, 377 | context, 378 | { 379 | a: 'a', 380 | someObject: { 381 | b: 'b', 382 | }, 383 | c: 'c', 384 | }, 385 | {} 386 | ); 387 | 388 | expect(context).toEqual({ 389 | a: 'a', 390 | b: 'b', 391 | c: 'c', 392 | }); 393 | }); 394 | it('should map a json object to a context property with map option', () => { 395 | const context = {}; 396 | 397 | Serializer.deserialize( 398 | { 399 | prototype: { 400 | _serializeMap: { 401 | a: {name: 'a'}, 402 | b: {name: 'b', map: 'mapped_b'}, 403 | c: {name: 'c'}, 404 | }, 405 | }, 406 | }, 407 | context, 408 | { 409 | a: 'a', 410 | mapped_b: 'b', 411 | c: 'c', 412 | }, 413 | {} 414 | ); 415 | 416 | expect(context).toEqual({ 417 | a: 'a', 418 | b: 'b', 419 | c: 'c', 420 | }); 421 | }); 422 | it('should map a json object to a context property with list option', () => { 423 | const context = {}; 424 | 425 | Serializer.deserialize( 426 | { 427 | prototype: { 428 | _serializeMap: { 429 | a: {name: 'a'}, 430 | b: {name: 'b', list: true}, 431 | c: {name: 'c'}, 432 | }, 433 | }, 434 | }, 435 | context, 436 | { 437 | a: 'a', 438 | b: ['a', 'b', 'c'], 439 | c: 'c', 440 | }, 441 | {} 442 | ); 443 | 444 | expect(context).toEqual({ 445 | a: 'a', 446 | b: ['a', 'b', 'c'], 447 | c: 'c', 448 | }); 449 | }); 450 | it('should map a json object to a context property with type option', () => { 451 | const test = function Test(): any { 452 | this.deserialize = () => { 453 | this.test = 'test'; 454 | }; 455 | }; 456 | 457 | const context: any = {}; 458 | 459 | Serializer.deserialize( 460 | { 461 | prototype: { 462 | _serializeMap: { 463 | a: {name: 'a'}, 464 | b: {name: 'b', type: test}, 465 | c: {name: 'c'}, 466 | }, 467 | }, 468 | }, 469 | context, 470 | { 471 | a: 'a', 472 | b: {test: 'test'}, 473 | c: 'c', 474 | }, 475 | {} 476 | ); 477 | 478 | expect(context.a).toEqual('a'); 479 | expect(context.b.test).toEqual('test'); 480 | expect(context.c).toEqual('c'); 481 | }); 482 | it('should map a json object to a context property with both list and type options', () => { 483 | const test = function Test(): any { 484 | this.deserialize = function (value: any) { 485 | this.test = value.test; 486 | }; 487 | }; 488 | 489 | const context: any = {}; 490 | 491 | Serializer.deserialize( 492 | { 493 | prototype: { 494 | _serializeMap: { 495 | a: {name: 'a'}, 496 | b: {name: 'b', type: test, list: true}, 497 | c: {name: 'c'}, 498 | }, 499 | }, 500 | }, 501 | context, 502 | { 503 | a: 'a', 504 | b: [{test: 'test1'}, {test: 'test2'}, {test: 'test3'}], 505 | c: 'c', 506 | }, 507 | {} 508 | ); 509 | 510 | expect(context.a).toEqual('a'); 511 | expect(context.b[0].test).toEqual('test1'); 512 | expect(context.b[1].test).toEqual('test2'); 513 | expect(context.b[2].test).toEqual('test3'); 514 | expect(context.c).toEqual('c'); 515 | }); 516 | it('should map a json object to a context property without some properties', () => { 517 | const test = function Test(): void { 518 | this.serialize = (v: any) => { 519 | return { 520 | test: v.test, 521 | }; 522 | }; 523 | }; 524 | 525 | const context: any = {}; 526 | 527 | Serializer.deserialize( 528 | { 529 | prototype: { 530 | _serializeMap: { 531 | a: {name: 'a'}, 532 | b: {name: 'b', type: test, optional: true}, 533 | c: {name: 'c', optional: true}, 534 | d: {name: 'd', list: true, optional: true}, 535 | }, 536 | }, 537 | }, 538 | context, 539 | { 540 | a: 'a', 541 | } 542 | ); 543 | 544 | expect(context.a).toEqual('a'); 545 | expect(context.b).toBeNull(); 546 | expect(context.c).toBeNull(); 547 | expect(context.d).toEqual([]); 548 | }); 549 | it('should return a json object with the right negative values', () => { 550 | const context: any = {}; 551 | 552 | Serializer.deserialize( 553 | { 554 | prototype: { 555 | _serializeMap: { 556 | a: {name: 'a'}, 557 | b: {name: 'b'}, 558 | c: {name: 'c'}, 559 | d: {name: 'd'}, 560 | e: {name: 'e'}, 561 | f: {name: 'f'}, 562 | }, 563 | }, 564 | }, 565 | context, 566 | { 567 | a: true, 568 | b: false, 569 | c: null, 570 | d: undefined, 571 | e: 0, 572 | f: 1, 573 | } 574 | ); 575 | 576 | expect(context.a).toBeTruthy(); 577 | expect(context.b).toBeFalsy(); 578 | expect(context.c).toBeNull(); 579 | expect(context.d).toBeNull(); 580 | expect(context.e).toEqual(0); 581 | expect(context.f).toEqual(1); 582 | }); 583 | it('should return a json object with the right negative values', () => { 584 | const context: any = {}; 585 | 586 | Serializer.deserialize( 587 | { 588 | prototype: { 589 | _serializeMap: { 590 | a: {name: 'a'}, 591 | b: {name: 'b'}, 592 | c: {name: 'c'}, 593 | d: {name: 'd'}, 594 | e: {name: 'e'}, 595 | f: {name: 'f'}, 596 | g: {optional: true, map: 'mapName', root: 'rootName'} 597 | }, 598 | }, 599 | }, 600 | context, 601 | { 602 | a: true, 603 | b: false, 604 | c: null, 605 | d: undefined, 606 | e: 0, 607 | f: 1, 608 | } 609 | ); 610 | 611 | expect(context.a).toBeTruthy(); 612 | expect(context.b).toBeFalsy(); 613 | expect(context.c).toBeNull(); 614 | expect(context.d).toBeNull(); 615 | expect(context.e).toEqual(0); 616 | expect(context.f).toEqual(1); 617 | }); 618 | }); 619 | --------------------------------------------------------------------------------