|
@@ -105,12 +105,12 @@ const mapping = {
|
|
*/
|
|
*/
|
|
|
|
|
|
import { Injectable } from '@angular/core';
|
|
import { Injectable } from '@angular/core';
|
|
-import * as _ from 'lodash';
|
|
|
|
|
|
+import { get, set, unset, cloneDeep, merge, every, findLast, sortBy } from 'lodash';
|
|
|
|
|
|
@Injectable()
|
|
@Injectable()
|
|
export class ModelMapperService {
|
|
export class ModelMapperService {
|
|
|
|
|
|
- mapping: StringMap;
|
|
|
|
|
|
+ mapping: StringMap<any>;
|
|
errors: string[] = [];
|
|
errors: string[] = [];
|
|
|
|
|
|
debug = false;
|
|
debug = false;
|
|
@@ -118,21 +118,21 @@ export class ModelMapperService {
|
|
|
|
|
|
constructor() { }
|
|
constructor() { }
|
|
|
|
|
|
- public setMapping(mapping: StringMap) {
|
|
|
|
|
|
+ public setMapping(mapping: StringMap<any>) {
|
|
this.mapping = mapping;
|
|
this.mapping = mapping;
|
|
}
|
|
}
|
|
|
|
|
|
public forwardMap = (
|
|
public forwardMap = (
|
|
- model: StringMap,
|
|
|
|
- mapping: StringMap = this.mapping,
|
|
|
|
|
|
+ model: StringMap<any>,
|
|
|
|
+ mapping: StringMap<any> = this.mapping,
|
|
mapMissing = false,
|
|
mapMissing = false,
|
|
res = {},
|
|
res = {},
|
|
stack = []
|
|
stack = []
|
|
- ): StringMap => {
|
|
|
|
|
|
+ ): StringMap<any> => {
|
|
// Map the input model onto res using the supplied mapping
|
|
// Map the input model onto res using the supplied mapping
|
|
Object.keys(model).forEach(key => {
|
|
Object.keys(model).forEach(key => {
|
|
const absPath = [...stack, key].join('.');
|
|
const absPath = [...stack, key].join('.');
|
|
- const _mapping = _.get(mapping, key, mapMissing ? key : false);
|
|
|
|
|
|
+ const _mapping = get(mapping, key, mapMissing ? key : false);
|
|
if (_mapping) {
|
|
if (_mapping) {
|
|
const mappingType = this.resolveMappingType(_mapping);
|
|
const mappingType = this.resolveMappingType(_mapping);
|
|
switch(mappingType) {
|
|
switch(mappingType) {
|
|
@@ -155,7 +155,7 @@ export class ModelMapperService {
|
|
this.clog('Target Path', targetPath);
|
|
this.clog('Target Path', targetPath);
|
|
const func = storedContainerMapping.find(m => typeof m === 'function');
|
|
const func = storedContainerMapping.find(m => typeof m === 'function');
|
|
const funcRes = func(model[key]);
|
|
const funcRes = func(model[key]);
|
|
- const fullRes = targetPath ? _.set({}, targetPath, funcRes) : funcRes; // Construct an update object from the model's root
|
|
|
|
|
|
+ const fullRes = targetPath ? set({}, targetPath, funcRes) : funcRes; // Construct an update object from the model's root
|
|
this.forwardMap(fullRes, {}, true, res);
|
|
this.forwardMap(fullRes, {}, true, res);
|
|
} catch (e) {
|
|
} catch (e) {
|
|
this.clog(e);
|
|
this.clog(e);
|
|
@@ -184,23 +184,23 @@ export class ModelMapperService {
|
|
}
|
|
}
|
|
|
|
|
|
public lazyForwardMap = (
|
|
public lazyForwardMap = (
|
|
- model: StringMap,
|
|
|
|
- mapping: StringMap = this.mapping
|
|
|
|
- ): StringMap => this.forwardMap(model, mapping, true)
|
|
|
|
|
|
+ model: StringMap<any>,
|
|
|
|
+ mapping: StringMap<any> = this.mapping
|
|
|
|
+ ): StringMap<any> => this.forwardMap(model, mapping, true)
|
|
|
|
|
|
public reverseMap = (
|
|
public reverseMap = (
|
|
- model: StringMap,
|
|
|
|
- mapping: StringMap = this.mapping,
|
|
|
|
|
|
+ model: StringMap<any>,
|
|
|
|
+ mapping: StringMap<any> = this.mapping,
|
|
mapMissing = false,
|
|
mapMissing = false,
|
|
pathsToDelete = [],
|
|
pathsToDelete = [],
|
|
stack = []
|
|
stack = []
|
|
- ): StringMap => {
|
|
|
|
|
|
+ ): StringMap<any> => {
|
|
// pathToDelete contains a list of source paths to delete from the model, leaving the missing to be straight-mapped
|
|
// pathToDelete contains a list of source paths to delete from the model, leaving the missing to be straight-mapped
|
|
if (!mapping) {
|
|
if (!mapping) {
|
|
throw new Error('Attempting to use Model Mapper without mapping');
|
|
throw new Error('Attempting to use Model Mapper without mapping');
|
|
}
|
|
}
|
|
const res = {};
|
|
const res = {};
|
|
- const modelClone = stack.length ? model : _.cloneDeep(model); // Clone the model unless inside a recursive call
|
|
|
|
|
|
+ const modelClone = stack.length ? model : cloneDeep(model); // Clone the model unless inside a recursive call
|
|
Object.keys(mapping).filter(key => key !== '__').forEach(key => {
|
|
Object.keys(mapping).filter(key => key !== '__').forEach(key => {
|
|
const dataMapping = mapping[key];
|
|
const dataMapping = mapping[key];
|
|
const mappingType = this.resolveMappingType(dataMapping);
|
|
const mappingType = this.resolveMappingType(dataMapping);
|
|
@@ -208,7 +208,7 @@ export class ModelMapperService {
|
|
case 'simple':
|
|
case 'simple':
|
|
{
|
|
{
|
|
// A simple path
|
|
// A simple path
|
|
- const value = _.get(modelClone, dataMapping);
|
|
|
|
|
|
+ const value = get(modelClone, dataMapping);
|
|
if (typeof value !== 'undefined') {
|
|
if (typeof value !== 'undefined') {
|
|
this.deepSet(res, key, value);
|
|
this.deepSet(res, key, value);
|
|
}
|
|
}
|
|
@@ -251,9 +251,9 @@ export class ModelMapperService {
|
|
const deepestPathsLast = this.sortByPathDepth(pathsToDelete);
|
|
const deepestPathsLast = this.sortByPathDepth(pathsToDelete);
|
|
while(deepestPathsLast.length) {
|
|
while(deepestPathsLast.length) {
|
|
const path = deepestPathsLast.pop();
|
|
const path = deepestPathsLast.pop();
|
|
- const t = typeof _.get(modelClone, path);
|
|
|
|
|
|
+ const t = typeof get(modelClone, path);
|
|
if (t === 'number' || t === 'string' || t === 'boolean') {
|
|
if (t === 'number' || t === 'string' || t === 'boolean') {
|
|
- _.unset(modelClone, path);
|
|
|
|
|
|
+ unset(modelClone, path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const modelRemainder = this.deepCleanse(modelClone);
|
|
const modelRemainder = this.deepCleanse(modelClone);
|
|
@@ -265,9 +265,9 @@ export class ModelMapperService {
|
|
}
|
|
}
|
|
|
|
|
|
public lazyReverseMap = (
|
|
public lazyReverseMap = (
|
|
- model: StringMap,
|
|
|
|
- mapping: StringMap = this.mapping
|
|
|
|
- ): StringMap => this.reverseMap(model, mapping, true)
|
|
|
|
|
|
+ model: StringMap<any>,
|
|
|
|
+ mapping: StringMap<any> = this.mapping
|
|
|
|
+ ): StringMap<any> => this.reverseMap(model, mapping, true)
|
|
|
|
|
|
public getErrors() {
|
|
public getErrors() {
|
|
return this.errors;
|
|
return this.errors;
|
|
@@ -285,12 +285,12 @@ export class ModelMapperService {
|
|
if (
|
|
if (
|
|
Array.isArray(mappingPath)
|
|
Array.isArray(mappingPath)
|
|
&& mappingPath.length === 2
|
|
&& mappingPath.length === 2
|
|
- && _.every(mappingPath, m => typeof m === 'function')
|
|
|
|
|
|
+ && every(mappingPath, m => typeof m === 'function')
|
|
||
|
|
||
|
|
Array.isArray(mappingPath)
|
|
Array.isArray(mappingPath)
|
|
&& mappingPath.length === 3
|
|
&& mappingPath.length === 3
|
|
&& (typeof mappingPath[0] === 'number' || typeof mappingPath[0] === 'string')
|
|
&& (typeof mappingPath[0] === 'number' || typeof mappingPath[0] === 'string')
|
|
- && _.every(mappingPath.slice(1), m => typeof m === 'function')
|
|
|
|
|
|
+ && every(mappingPath.slice(1), m => typeof m === 'function')
|
|
) {
|
|
) {
|
|
mappingType = 'functional';
|
|
mappingType = 'functional';
|
|
} else {
|
|
} else {
|
|
@@ -309,9 +309,9 @@ export class ModelMapperService {
|
|
if (path === '/') {
|
|
if (path === '/') {
|
|
arg = model; // '/' indicates use the entire model
|
|
arg = model; // '/' indicates use the entire model
|
|
} else {
|
|
} else {
|
|
- arg = _.get(model, path.replace(/^\//, ''));
|
|
|
|
|
|
+ arg = get(model, path.replace(/^\//, ''));
|
|
}
|
|
}
|
|
- const func = _.findLast(fnMapping, m => typeof m === 'function');
|
|
|
|
|
|
+ const func = findLast(fnMapping, m => typeof m === 'function');
|
|
try {
|
|
try {
|
|
result = func(arg);
|
|
result = func(arg);
|
|
} catch(e) {
|
|
} catch(e) {
|
|
@@ -324,25 +324,25 @@ export class ModelMapperService {
|
|
// NOTE: This mutates the incoming object at the moment, so doesn't reed to return a value
|
|
// NOTE: This mutates the incoming object at the moment, so doesn't reed to return a value
|
|
// Maybe rewrite with a more functional approach?
|
|
// Maybe rewrite with a more functional approach?
|
|
// Will deep merge where possible
|
|
// Will deep merge where possible
|
|
- const currentVal = _.get(obj, mappingPath);
|
|
|
|
|
|
+ const currentVal = get(obj, mappingPath);
|
|
const t = typeof currentVal;
|
|
const t = typeof currentVal;
|
|
if (t === 'undefined') {
|
|
if (t === 'undefined') {
|
|
- _.set(obj, mappingPath, valueToSet);
|
|
|
|
|
|
+ set(obj, mappingPath, valueToSet);
|
|
} else if (t === 'number' || t === 'string' || t === 'boolean') {
|
|
} else if (t === 'number' || t === 'string' || t === 'boolean') {
|
|
// We can only overwrite existing scalar values, not deep merge
|
|
// We can only overwrite existing scalar values, not deep merge
|
|
if (overwrite) {
|
|
if (overwrite) {
|
|
this.errors.push('WARNING: Overwriting scalar value at', mappingPath);
|
|
this.errors.push('WARNING: Overwriting scalar value at', mappingPath);
|
|
- _.set(obj, mappingPath, valueToSet);
|
|
|
|
|
|
+ set(obj, mappingPath, valueToSet);
|
|
} else {
|
|
} else {
|
|
this.errors.push('WARNING: Discarding scalar value at', mappingPath, 'as exisiting non-scalar value would be overwritten');
|
|
this.errors.push('WARNING: Discarding scalar value at', mappingPath, 'as exisiting non-scalar value would be overwritten');
|
|
}
|
|
}
|
|
} else if (t === 'object' && typeof valueToSet === 'object') {
|
|
} else if (t === 'object' && typeof valueToSet === 'object') {
|
|
// Deep merge
|
|
// Deep merge
|
|
- let merged = _.merge(currentVal, valueToSet);
|
|
|
|
|
|
+ let merged = merge(currentVal, valueToSet);
|
|
if (!overwrite) {
|
|
if (!overwrite) {
|
|
- merged = _.merge(merged, currentVal); // Is there a better way?
|
|
|
|
|
|
+ merged = merge(merged, currentVal); // Is there a better way?
|
|
}
|
|
}
|
|
- _.set(obj, mappingPath, merged);
|
|
|
|
|
|
+ set(obj, mappingPath, merged);
|
|
} else {
|
|
} else {
|
|
this.errors.push('WARNING: Could not merge', typeof valueToSet, 'with object');
|
|
this.errors.push('WARNING: Could not merge', typeof valueToSet, 'with object');
|
|
}
|
|
}
|
|
@@ -367,7 +367,7 @@ export class ModelMapperService {
|
|
return cleansedObj;
|
|
return cleansedObj;
|
|
}
|
|
}
|
|
|
|
|
|
- private sortByPathDepth = pathArr => _.sortBy(pathArr, p => p.split('.').length);
|
|
|
|
|
|
+ private sortByPathDepth = pathArr => sortBy(pathArr, p => p.split('.').length);
|
|
|
|
|
|
private clearErrors() {
|
|
private clearErrors() {
|
|
this.errors = [];
|
|
this.errors = [];
|