Added a transformer to convert between data types
This commit is contained in:
parent
7287eb024c
commit
7fcbc69235
|
@ -2,12 +2,12 @@ import { ModelTransformation, ModelTransformer } from '../model-transformer';
|
|||
import { DummyModel } from '../../__test_data__/dummy-model';
|
||||
|
||||
class DummyTransformation implements ModelTransformation {
|
||||
public readonly order: number;
|
||||
public readonly fromModelOrder: number;
|
||||
|
||||
private readonly fn: ( ( p: any ) => any ) | null;
|
||||
|
||||
public constructor( order: number, fn: ( ( p: any ) => any ) | null ) {
|
||||
this.order = order;
|
||||
this.fromModelOrder = order;
|
||||
this.fn = fn;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@ import { Model } from '../models/model';
|
|||
export interface ModelTransformation {
|
||||
/**
|
||||
* The order of execution for the transformer.
|
||||
* - For "toModel" higher numbers execute later.
|
||||
* - For "fromModel" the order is reversed.
|
||||
* - For "fromModel" higher numbers execute later.
|
||||
* - For "toModel" the order is reversed.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
readonly order: number;
|
||||
readonly fromModelOrder: number;
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
|
@ -34,12 +34,18 @@ export interface ModelTransformation {
|
|||
}
|
||||
|
||||
/**
|
||||
* An enum for more easily defining transformation order values.
|
||||
* An enum for defining the "toModel" transformation order values.
|
||||
*/
|
||||
export enum TransformationOrder {
|
||||
First = 0,
|
||||
Normal = 500000,
|
||||
Last = 1000000
|
||||
Last = 1000000,
|
||||
|
||||
/**
|
||||
* A special value reserved for transformations that MUST come after all orders due to
|
||||
* the way that they destroy the property keys or values.
|
||||
*/
|
||||
VeryLast = 2000000
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +67,7 @@ export class ModelTransformer< T extends Model > {
|
|||
*/
|
||||
public constructor( transformations: ModelTransformation[] ) {
|
||||
// Ensure that the transformations are sorted by priority.
|
||||
transformations.sort( ( a, b ) => ( a.order > b.order ) ? 1 : -1 );
|
||||
transformations.sort( ( a, b ) => ( a.fromModelOrder > b.fromModelOrder ) ? 1 : -1 );
|
||||
|
||||
this.transformations = transformations;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import { PropertyType, PropertyTypeTransformation } from '../property-type-transformation';
|
||||
|
||||
describe( 'PropertyTypeTransformation', () => {
|
||||
let transformation: PropertyTypeTransformation;
|
||||
|
||||
beforeEach( () => {
|
||||
transformation = new PropertyTypeTransformation(
|
||||
{
|
||||
string: PropertyType.String,
|
||||
integer: PropertyType.Integer,
|
||||
float: PropertyType.Float,
|
||||
boolean: PropertyType.Boolean,
|
||||
date: PropertyType.Date,
|
||||
callback: ( value: string ) => 'Transformed-' + value,
|
||||
},
|
||||
);
|
||||
} );
|
||||
|
||||
it( 'should convert strings', () => {
|
||||
let transformed = transformation.toModel( { string: 'Test' } );
|
||||
|
||||
expect( transformed.string ).toStrictEqual( 'Test' );
|
||||
|
||||
transformed = transformation.fromModel( { string: 'Test' } );
|
||||
|
||||
expect( transformed.string ).toStrictEqual( 'Test' );
|
||||
} );
|
||||
|
||||
it( 'should convert integers', () => {
|
||||
let transformed = transformation.toModel( { integer: '100' } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( 100 );
|
||||
|
||||
transformed = transformation.fromModel( { integer: 100 } );
|
||||
|
||||
expect( transformed.integer ).toStrictEqual( '100' );
|
||||
} );
|
||||
|
||||
it( 'should convert floats', () => {
|
||||
let transformed = transformation.toModel( { float: '2.5' } );
|
||||
|
||||
expect( transformed.float ).toStrictEqual( 2.5 );
|
||||
|
||||
transformed = transformation.fromModel( { float: 2.5 } );
|
||||
|
||||
expect( transformed.float ).toStrictEqual( '2.5' );
|
||||
} );
|
||||
|
||||
it( 'should convert booleans', () => {
|
||||
let transformed = transformation.toModel( { boolean: 'true' } );
|
||||
|
||||
expect( transformed.boolean ).toStrictEqual( true );
|
||||
|
||||
transformed = transformation.fromModel( { boolean: false } );
|
||||
|
||||
expect( transformed.boolean ).toStrictEqual( 'false' );
|
||||
} );
|
||||
|
||||
it( 'should convert dates', () => {
|
||||
let transformed = transformation.toModel( { date: '2020-11-06T03:11:41.000Z' } );
|
||||
|
||||
expect( transformed.date ).toStrictEqual( new Date( '2020-11-06T03:11:41.000Z' ) );
|
||||
|
||||
transformed = transformation.fromModel( { date: new Date( '2020-11-06T03:11:41.000Z' ) } );
|
||||
|
||||
expect( transformed.date ).toStrictEqual( '2020-11-06T03:11:41.000Z' );
|
||||
} );
|
||||
|
||||
it( 'should use conversion callbacks', () => {
|
||||
let transformed = transformation.toModel( { callback: 'Test' } );
|
||||
|
||||
expect( transformed.callback ).toStrictEqual( 'Transformed-Test' );
|
||||
|
||||
transformed = transformation.fromModel( { callback: 'Test' } );
|
||||
|
||||
expect( transformed.callback ).toStrictEqual( 'Transformed-Test' );
|
||||
} );
|
||||
} );
|
|
@ -11,7 +11,7 @@ type AdditionalProperties = { [ key: string ]: any };
|
|||
* a default value if it is not already set.
|
||||
*/
|
||||
export class AddPropertyTransformation implements ModelTransformation {
|
||||
public readonly order = TransformationOrder.Normal;
|
||||
public readonly fromModelOrder = TransformationOrder.Normal;
|
||||
|
||||
/**
|
||||
*The additional properties to add when executing toModel.
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ModelTransformation } from '../model-transformer';
|
|||
type TransformationCallback = ( properties: any ) => any;
|
||||
|
||||
export class CustomTransformation implements ModelTransformation {
|
||||
public readonly order: number;
|
||||
public readonly fromModelOrder: number;
|
||||
|
||||
/**
|
||||
* The hook to run for toModel.
|
||||
|
@ -40,7 +40,7 @@ export class CustomTransformation implements ModelTransformation {
|
|||
toHook: TransformationCallback | null,
|
||||
fromHook: TransformationCallback | null,
|
||||
) {
|
||||
this.order = order;
|
||||
this.fromModelOrder = order;
|
||||
this.toHook = toHook;
|
||||
this.fromHook = fromHook;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ export class KeyChangeTransformation< T extends Model > implements ModelTransfor
|
|||
* Ensure that this transformation always happens at the very end since it changes the keys
|
||||
* in the transformed object.
|
||||
*/
|
||||
public readonly order = TransformationOrder.Last + TransformationOrder.Last;
|
||||
public readonly fromModelOrder = TransformationOrder.VeryLast + 1;
|
||||
|
||||
/**
|
||||
* The key change transformations that this object should perform.
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
import { ModelTransformation, TransformationOrder } from '../model-transformer';
|
||||
|
||||
/**
|
||||
* An enum defining all of the property types that we might want to transform.
|
||||
*
|
||||
* @enum {number}
|
||||
*/
|
||||
export enum PropertyType {
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Boolean,
|
||||
Date,
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that can be used to transform property types.
|
||||
*
|
||||
* @callback PropertyTypeCallback
|
||||
* @param {*} value The value to transform.
|
||||
* @return {*} The transformed value.
|
||||
*/
|
||||
type PropertyTypeCallback = ( value: any ) => any;
|
||||
|
||||
/**
|
||||
* The types for all of a model's properties.
|
||||
*
|
||||
* @typedef PropertyTypes
|
||||
* @alias Object.<string,PropertyType>
|
||||
*/
|
||||
type PropertyTypes = { [ key: string ]: PropertyType | PropertyTypeCallback };
|
||||
|
||||
/**
|
||||
* A model transformer for converting property types between representation formats.
|
||||
*/
|
||||
export class PropertyTypeTransformation implements ModelTransformation {
|
||||
/**
|
||||
* We want the type transformation to take place after all of the others,
|
||||
* since they may be operating on internal data types.
|
||||
*/
|
||||
public readonly fromModelOrder = TransformationOrder.VeryLast;
|
||||
|
||||
/**
|
||||
* The property types we will want to transform.
|
||||
*
|
||||
* @type {PropertyTypes}
|
||||
* @private
|
||||
*/
|
||||
private readonly types: PropertyTypes;
|
||||
|
||||
/**
|
||||
* Creates a new transformation.
|
||||
*
|
||||
* @param {PropertyTypes} types The property types we want to transform.
|
||||
*/
|
||||
public constructor( types: PropertyTypes ) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from model properties to raw properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public fromModel( properties: any ): any {
|
||||
for ( const key in this.types ) {
|
||||
if ( ! properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
const value = properties[ key ];
|
||||
|
||||
const type = this.types[ key ];
|
||||
if ( type instanceof Function ) {
|
||||
properties[ key ] = type( value );
|
||||
continue;
|
||||
}
|
||||
|
||||
properties[ key ] = this.convertFrom( value, type );
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a transformation from raw properties to model properties.
|
||||
*
|
||||
* @param {*} properties The properties to transform.
|
||||
* @return {*} The transformed properties.
|
||||
*/
|
||||
public toModel( properties: any ): any {
|
||||
for ( const key in this.types ) {
|
||||
if ( ! properties.hasOwnProperty( key ) ) {
|
||||
continue;
|
||||
}
|
||||
const value = properties[ key ];
|
||||
|
||||
const type = this.types[ key ];
|
||||
if ( type instanceof Function ) {
|
||||
properties[ key ] = type( value );
|
||||
continue;
|
||||
}
|
||||
|
||||
properties[ key ] = this.convertTo( value, type );
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given value into the requested type.
|
||||
*
|
||||
* @param {*} value The value to transform.
|
||||
* @param {PropertyType} type The type to transform it into.
|
||||
* @return {*} The converted type.
|
||||
* @private
|
||||
*/
|
||||
private convertTo( value: string, type: PropertyType ): any {
|
||||
switch ( type ) {
|
||||
case PropertyType.String: return String( value );
|
||||
case PropertyType.Integer: return parseInt( value );
|
||||
case PropertyType.Float: return parseFloat( value );
|
||||
case PropertyType.Boolean: return Boolean( value );
|
||||
case PropertyType.Date: return new Date( value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given type into a string.
|
||||
*
|
||||
* @param {*} value The value to transform.
|
||||
* @param {PropertyType} type The type to transform it into.
|
||||
* @return {*} The converted type.
|
||||
* @private
|
||||
*/
|
||||
private convertFrom( value: any, type: PropertyType ): string {
|
||||
switch ( type ) {
|
||||
case PropertyType.String:
|
||||
case PropertyType.Integer:
|
||||
case PropertyType.Float:
|
||||
case PropertyType.Boolean:
|
||||
return String( value );
|
||||
|
||||
case PropertyType.Date: return ( value as Date ).toISOString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue