UNPKG

auto-mapping

Version:

map and convert object in typescript

322 lines (300 loc) 9.96 kB
# auto-mapping Map and convert objects automatically in typescript. # Features * Map object to an instance of a class by annotation; * Map object to another object which only has the mapped property; * Convert property types automatically; * Custom conversion; * Multiple data source mappings; * Map extensions; # Installation `$ npm install --save auto-mapping` # Getting started Please import the **reflect-metadata** module in the project entry file before use. To enable experimental support for decorators, you must enable the experimentalDecorators compiler option either on the command line or in your *tsconfig.json*: ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, } } ``` ```ts import 'reflect-metadata'; import { mapping, map, select } from 'auto-mapping'; class Person { @mapping({ type: String }) name: string; @mapping(Boolean) // short form gender: boolean; @mapping({ path: 'others.number', type: Number }) age: number; extraData = 1; } const data={ name: 'fisher', gender: 1, others: { number: '18' }, }; console.info("Mapped result:", map(data, Person)); console.info("Selected result:", select(data, Person)); ``` ```sh # output Mapped result: Person { extraData: 1, name: 'fisher', gender: true, age: 18 } Selected result: { name: 'fisher', gender: true, age: 18 } ``` # Use with reflect-metadata If you have already imported **reflect-metadata** module into your project, it will infer type automatic except array type. The array type must declare the **type** parameter at any time. ```ts import 'reflect-metadata'; import { mapping, map } from 'auto-mapping'; class Person { @mapping() name: string; @mapping() gender: boolean; @mapping({ path: 'others.number' }) age: number } const data={ name: 'fisher', gender: 1, others: { number: '18' }, }; console.info(map(data, Person)); ``` ```sh # output Person { name: 'fisher', gender: true, age: 18 } ``` # API ## @mapping(options: object | function) Configuring property mapping information, If the argument is a function, then it is equivalent to **{ type: options }**. * **domain**(string): The parent path of current property, the option domain will be ignored when used with path. * **type**(function | [function]): The property decalre type, it is always necessary if the property type is an array. Tt is optional if the module 'refleat-metadata' has been imported in your project. It also can be used to set custom conversion function, it will use default value if the conversion throws an error. * **path**(string): The property path in the source object, such as 'a.b.c','a.b[0].c', default is the current path name. Use the dot symbol ('.') to indicates the current path. * **order**(number): The order for the property generated, default is 0. * **source**(string): The source object name, it is required if you want to map data from multiple data sources. * **default**(any): Default value, multiple data sources can specify multiple different default values. *** ## map(source:object, constructor:function, options?:object) Map an object to an instance of the specified type. * **source**(object): Data source object * **constructor**(function): The type of instance, the constructor function of the class * **options**(string, optional): Mapping options. * **source**(string): The source object name, it is required if you want to map data from multiple data sources. * **useDefaultSource**(boolean): Use the default mapping configuration when the current source configuration is missing, default is **true**. * **nullable**(boolean): Whether to allow the value to be set to null. * **allowNaN**(booelan): Whether to allow the value to be set to NaN. * **converters**(Map): Custom conversion function, only valid during the current mapping. ## map.setDefaultConverter(type:function, converter:function) Set the global conversion function for the specified type * **type**(function): The type of value to convert. * **converter**(function): The conversion function. > The function **setDefaultConverter** will affect the global type conversion. If you want to use it locally, please set the **converters** option of the **map** function. ```ts import "reflect-metadata"; import { mapping, map } from "auto-mapping"; map.setDefaultConverter(String, (val) => (val === undefined || val === null) ? '' : String(val)); map.setDefaultConverter(Number, (val) => isNaN(Number(val)) ? 0 : Number(val)); class Person { @mapping() public cityName: string; @mapping(() => "default") public intro: string; @mapping() public num: number; } console.info(map({}, Person)); ``` ```sh # output Person { name: '', desc: 'default', num: 0 } ``` *** # Array Because the type of the array cannot be automatically derived, the type parameter must be specified at all times and the value must be an array. ```ts import { mapping, map } from 'auto-mapping'; class ArrayTest { @mapping({ type: [Number] }) numbers: number[]; } const data={ numbers:['1','2','3'] }; const result = map(data, ArrayTest); console.info(result); ``` ```sh # output ArrayTest { numbers: [ 1, 2, 3 ] } ``` If an property is declared as array, but the source value is not an array, the result is wrapped into an array. ```ts import { mapping, map } from 'auto-mapping'; class ArrayTest { @mapping({ type: [Number], path:'number' }) numbers: number[]; } const data={ number: '1' }; console.info(map(data, ArrayTest)); ``` ```sh # output ArrayTest { numbers: [ 1 ] } ``` # Multiple Data Source The default data source mapping config named **default**, you can set multiple configurations by **source** option to map multiple data sources. ```ts import { mapping, map } from 'auto-mapping'; class Person { @mapping() @mapping({ source: 'other', path: 'person.name' }) name: string; } const dataSource1 = { name: 'fisher' }; const dataSource2 = { person: { name: 'jack' } }; const result1 = map(dataSource1, Person); const result2 = map(dataSource2, Person, { source: 'other' }); console.info(result1, result2); ``` ```sh # output Person { name: 'fisher' } Person { name: 'jack' } ``` # Custom Conversion The signature of the custom conversion function is as follows: ```ts function(value: any, source: any, dest: any, options?: object){} ``` * value(any): The value which current path matched in the source object; * source(any): The source object; * dest(any): The instance of the dest class; * options(object, optional): Mapping options; Custom conversion function can be set by type parameter. If you just pass a function to the annotation, like this **mapping(fn)**, then it is equivalent to **mapping({ type: fn })**. ```ts import { mapping, map } from 'auto-mapping'; function trim(value: string) { return value && value.trim(); } function fullName(_value: any, source: any) { return `${source.firstName} ${source.lastName}`; } function info(_value: any, _source: any, dest: Person) { return `I am ${dest.name} who come from ${dest.city}`; } class Person { age: number = 18; @mapping(fullName) name: string; @mapping({ type: trim, path: 'city.cityName' }) city: string; @mapping(info) info: string; } const dataSource = { firstName: 'Lei', lastName: 'Lee', city: { cityId: 1, cityName: ' NEW YORK ' }, }; console.info(map(dataSource, Person)); ``` ```sh # ouput Person { age: 18, name: 'Lei Lee', city: 'NEW YORK', info: 'I am Lei Lee who come from NEW YORK.' } ``` # Extensions # [MAPPING](src:object, options?:object){...} Do some processing before mapping. ```ts import "reflect-metadata"; import { mapping, map, MAPPING } from "auto-mapping"; class Person { @mapping() public num: number = 1; @mapping() public name: string; [MAPPING](src: any, options: any) { src.name = "fisher"; } } const result = map({ num: 10 }, Person); console.info(result, result instanceof Person); ``` ```sh # output Person { num: 100, name: 'fisher' } true ``` If the *MAPPING* function returns a value that is not undefined, it will be passed to the *map* method as a source object. # [MAPPED](src:object, options?:object){...} Sometimes you may need to do some extra finishing work, such as dynamically adding some properties, you can do this through the **MAPPED** symbol function. ```ts import "reflect-metadata"; import { mapping, map, MAPPED } from "auto-mapping"; class Person { @mapping() public gender: boolean; @mapping() public num: number = 1; [MAPPED](src: any, options: any) { // set value manually this.gender = true; // override original value this.num = 100; // dynamic assignment Object.assign(this, { a: 1, b: 2 }) } } const result = map({ num: 10 }, Person); console.info(result, result instanceof Person); ``` ```sh # output Person { num: 100, gender: true, a: 1, b: 2 } true ``` > :warning: Note that when using the *MAPPED* function, if the map result is null, then any property that accesses ***this*** will throw an error. If the function returns a value that is not undefined, it will replace the map result value. ```ts ... [MAPPED](src: any, options: any) { this.gender = true; this.num = 100; return Object.assign({}, this, { a: 1, b: 2 }) } ... ``` ```sh # output { num: 100, gender: true, a: 1, b: 2 } false ``` # Update Logs ## 1.3.0 * added `select` function. ## 1.2.1 * compatible for mini-programs. ## 1.2.0 * added mapping options *nullable*、*allowNaN*、*converters*; * added new api *map.setDefaultConverter* to config global default type conversion; ## 1.1.0 * refacted module using *reflect-metadata*; * added two extension methods *MAPPING* and *MAPPED*; * marked the *after* method as a deprecated method and replaced with the *MAPPED* method.