jsobjmapper
v1.1.19
Published
JS object to JS object mapper utilizing mapping strategies defined as JS objects
Downloads
9
Readme
jsobjmapper
A generic data mapper which gets a Javascript object (the source object) as input and creates a new object utilizing configurable mapping strategies. The source object is not modified. The strategies may adapt (change, replace, delete, add) properties (names and values) while creating the copy. This is called object mapping.
The core is the mapToStrategy(obj, mappingStrategy, options)
function in the obj2objMapper module. It recursively converts
the given source object (obj
) into a new object by applying the given mapping strategy object (mappingStrategy
) and returns the
new object (i.e. the result of the mapping).
Without a specified (or empty) strategy the mapping is trivial, i.e. a deep copy. However, the mapping can be controlled by providing a mapping strategy consisting of one or more mapping rules. The rules describe which properties must be copied, if and how the original property names and/or values are processed/changed/deleted, may introduce new properties (with some static or dynamically calculated values), and may completely restructure the shape the resulting object.
So in short, the original obj is mapped to a new object according to the given mapping strategy which consists of mapping rules.
How exactly a property is mapped is specified by the corresponding mapping rules object which is defined via the
__mappingRules__
property which in turn contains one or more mapping rules. See below for a list of all available rules.
Examples
Simple value override
This function call
mappingUtils.mapToStrategy(
{ foo: 123, bar: 555 }, // The object to map
// The mapping strategy:
{
foo: {
__mappingRules__: {
overrideValue: 456
}
},
bar: {}
}
)
returns
{ foo: 456, bar: 555 } // foo is converted, bar is copied as is
Property rename and dynamic value conversion
Considering this helper function
function myConverter(value) {
return value + 4
}
This function call
mappingUtils.mapToStrategy(
{ foo: 15, bar: null, baz: 5 }, // The object to map
// The mapping strategy:
{
foo: {
__mappingRules__: {
targetPropName: 'myDummy',
convert: origValue => origValue * 2
}
},
bar: {
__mappingRules__: {
defaultValue: 456,
convert: myConverter
}
},
baz: {
__mappingRules__: {
defaultValue: 456,
convert: myConverter
}
}
}
)
returns
{ myDummy: 30, bar: 460, baz: 9 }
Have a look in the tests (obj2objMapper.test.js) for many more examples ranging from simple to complex! It's the best place to learn about the object mapper and the mapping strategies.
Be assured that nearly anything is possible having the right mapping strategy!
Mapping rules
The mapping can be adapted according to the following scenarios respectively mapping rules:
Origin property is not available (i.e., the property is missing in obj). By default, the property is not mapped (i.e., the property is missing in the resulting object). => Use isNotAvailable with some value in order to have this property in the resulting object anyway with that value.
Origin property has the value undefined. By default, the property is mapped with value BLANK_DEFAULT. => Use isUndefined with some value to override the default with that value.
Origin property has the value null. By default, the property is mapped with value BLANK_DEFAULT. => Use isNull with some value to override the default with that value.
Origin property doesn't have the desired value. By default, the mapping is canonical, i.e. the value is copied as is. => Use overrideValue with some value to override the original value no matter what, even if the property is not existent in the source object in which case it is created in the resulting object. Note: overrideValue may be a literal value or a callback function which is passed the original value, the original property name, and the source object and must return the new value.
Origin property has the value undefined or null. By default, the property is mapped with value BLANK_DEFAULT. => Use defaultValue with some value to override the default with that value. Note: defaultValue is taken only if no overrideValue is set (because overrideValue has precedence) and the value defined by defaultValue is not null/undefined and the property, the defaultValue is specified for, is existent in the original object having value null or undefined.
Origin property doesn't have the desired name. By default, the property name is taken as is. => Use targetPropName with a non-null/non-undefined string to map the property to that new property name. Note: targetPropName may be a literal value or a callback function which is passed the original property name and the original value and must return the new property name.
Origin property value must be processed and mapped dynamically to a new value if the original value is neither null nor undefined. By default, the mapping is canonical, i.e. the value is copied as is. => Use convert with a callback function (or an array of callback functions) to transform the original value. The function is passed the original value, the destination propertyName, the source object, and the destination object (i.e., mapped object) and must return the new value. The convert function(s) is/are only executed if the original value of the property in question is neither null nor undefined.
Origin property value must be processed and mapped dynamically to a new value no matter whether or not the original value of the property has a null or undefined value, or is existent at all. By default, the mapping is canonical, i.e. the value is copied as is. => Use forceConvert with a callback function (or an array of callback functions) to transform the original value. The function is passed the original value, the destination propertyName, the source object, and the destination object (i.e., mapped object) and must return the new value. The convert function(s) is/are executed -- if given -- no matter whether the original value of the property has a null or undefined value, or is existent at all.
Origin property location differs from the desired location. By default, the mapped property has the same location (i.e. identical path in the data structure) as the origin property. => Use sourceRoot with a callback function which is passed the source object in order to have access to all properties. The function must return a sub-object of the source object which is used as source object for the property.
Origin property is mandatory (i.e. it must be present with a non-null/non-undefined value) and if not an error must be thrown. By default, the mapping is graceful and skips properties listed in the strategy but not available in the source object. => Use mandatory with value
true
to raise an error if the property is missing in the source object or has value null/undefined.You can validate the property values of the resulting object. By default, these values are not validated. => Use validateStrict or validateNonBlank to validate the value. If a validation fails, an error is thrown. validateStrict validates the value no matter what, even if it is
null
orundefined
or the respective property does not exist at all in the source object. validateNonBlank validates the value only if it is present and neither null nor undefined.Origin property value is an array and you want to apply mapping rules for each element in turn. By default, the original value (the array) is processed altogether. => Use iterateArray in order to be able to apply mapping rules for each element in turn. The values are wrapped in objects having the key 'element'. Note: iterateArray and subTree cannot be used together Example:
const sourceObj = { person: { name: 'Mark', skills: [ { name: 'cs', level: 9, since: 80 }, { name: 'se', level: 10, since: 123 }, { name: 'sa', level: 10, since: 70 }, { name: 'pm', level: 8, since: 50 }, { name: 'gm', level: 0, since: 0 }, ], }, } const destObj = { person: { name: 'Mark', skills: [ { label: 'CS', level: 9 }, { label: 'SE', level: 10 }, { label: 'SA', level: 10 }, { label: 'PM', level: 8 }, { label: 'GM', level: 0 }, ], }, } const strategy = { person: { name: {}, skills: { __mappingRules__: { iterateArray: { element: { name: { __mappingRules__: { targetPropName: 'label', convert: value => value.toUpperCase(), }, }, level: {}, }, }, }, }, }, } assert.deepEqual(mappingUtils.mapToStrategy(sourceObj, strategy), destObj)
You want to create a sub-structure (i.e. a property with an object as value) not present in the source object. By default, the structure of the source object is taken. => Use subTree in order to be able to create structures not present in the source object Note: iterateArray and subTree cannot be used together Example:
const sourceObj = { person: { firstname: 'Footy', lastname: 'Barium', job: 'se', dob: '1980-01-01', }, } const destObj = { person: { name: { firstname: 'Mark', lastname: 'Vz', }, job: 'se', dob: '1980-01-01', }, } const strategy = { person: { name: { __mappingRules__: { subTree: true, }, firstname: { __mappingRules__: { sourceRoot: sourceObj => sourceObj.person, }, }, lastname: { __mappingRules__: { sourceRoot: sourceObj => sourceObj.person, }, }, }, job: {}, dob: {}, }, } assert.deepEqual(mappingUtils.mapToStrategy(sourceObj, strategy), destObj)
You can prevent logging for a property regardless of the defined error level. By default, logging happens according to the defined error level. => Use doNotLog to silent logging for the respective property.
Options
The third parameter of the mapToStrategy
function carries configuration options. They are:
dataSourceIsFlat If the original data source (i.e., source object) is flat but a mapping to a nested structure is required (and hence the mapping strategy defines a nested structure) then pass this option with value
true
and also use thesubTree
mapping rule with valuetrue
for each sub tree defining a nested structure (object) to follow. TODO: add more docuerrorLevel TODO: add docu