Created
January 20, 2018 04:56
-
-
Save jlyman/381eb9569de74abf107c80460e2cf84c to your computer and use it in GitHub Desktop.
Javascript ES6 API to model mapper functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import assign from 'lodash/assign'; | |
/** | |
* Converts one type of object to another, in this case to/from our Models to | |
* plain API response objects. | |
* @param {object} sourceObj The source object, either a Model or a plain API response object | |
* @param {array} mapping An array of arrays, the inner arrays containing key mappings between objects. | |
* Inner array format can be either ['modelPropertyKey', ['apiPropertyKey'] or | |
* ['modelPropertyKey', apiToModelTransformer(), modelToApiTransformer()] | |
* @param {bool} isMappingModelToApi True if we are taking a Model object an | |
* mapping to an API response object, otherwise false. | |
*/ | |
function mappingReducer(sourceObj, mapping, isMappingModelToApi) { | |
const sourceMapIndex = isMappingModelToApi ? 0 : 1; | |
const targetMapIndex = isMappingModelToApi ? 1 : 0; | |
const lambdaMapIndex = isMappingModelToApi ? 2 : 1; | |
// Iterate through each element of the `mapping` object, | |
// and map the source property to the target property. | |
return mapping.reduce((targetObj, mapEl) => { | |
if (mapEl.length === 3) { | |
// We are using mapping functions to generate the result. | |
// Process and Object.assign the result. | |
if (mapEl[lambdaMapIndex] !== null) { | |
const result = mapEl[lambdaMapIndex](sourceObj); | |
assign(targetObj, result); | |
} | |
} else { | |
// Just a simple straight mapping conversion. | |
targetObj[mapEl[targetMapIndex]] = sourceObj[mapEl[sourceMapIndex]]; | |
} | |
return targetObj; | |
}, {}); | |
} | |
/** | |
* Converts a Model to an API response object | |
* @param {object} model The model to convert to a POJO | |
* @param {array} modelMap An array of arrays, the inner arrays containing key mappings between objects. | |
* Inner array format can be either ['modelPropertyKey', ['apiPropertyKey'] or | |
* ['modelPropertyKey', apiToModelTransformer(), modelToApiTransformer()] | |
*/ | |
export function mapModelToApi(model, modelMap) { | |
return mappingReducer(model, modelMap, true); | |
} | |
/** | |
* Converts a plain API response object to a Model. | |
* @param {object} apiObject The API response to convert to a Model | |
* @param {array} modelMap An array of arrays, the inner arrays containing key mappings between objects. | |
* Inner array format can be either ['modelPropertyKey', ['apiPropertyKey'] or | |
* ['modelPropertyKey', apiToModelTransformer(), modelToApiTransformer()] | |
* @param {Prototype} modelPrototype The type of model we are creating (e.g., Referral) | |
*/ | |
export function mapApiToModel(apiObject, modelMap, modelPrototype) { | |
const data = mappingReducer(apiObject, modelMap, false); | |
return new modelPrototype(data); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import expect from 'expect'; | |
import { mapModelToApi, mapApiToModel } from './modelMapper'; | |
function TestModel(data) { | |
this.testElement = data ? data.testElement : 'WRONG1'; | |
this.nestedElement = data ? data.nestedElement : 'WRONG2'; | |
} | |
const exampleTestModel = new TestModel({ | |
testElement: 'GOOD1', | |
nestedElement: { | |
nestedElement1: 'GOOD2', | |
nestedElement2: 'GOOD3', | |
}, | |
}); | |
const apiResponse = { | |
test_element: 'GOOD1', | |
nested_element_1: 'GOOD2', | |
nested_element_2: 'GOOD3', | |
}; | |
const mapping = [ | |
['testElement', 'test_element'], | |
[ | |
'nestedElement', | |
apiObj => ({ | |
nestedElement: { | |
nestedElement1: apiObj.nested_element_1, | |
nestedElement2: apiObj.nested_element_2, | |
}, | |
}), | |
modelObj => ({ | |
nested_element_1: modelObj.nestedElement.nestedElement1, | |
nested_element_2: modelObj.nestedElement.nestedElement2, | |
}), | |
], | |
]; | |
describe('Model mapper functions', () => { | |
describe('Model --> API response', () => { | |
it('should map basic elements directly', () => { | |
const result = mapModelToApi(exampleTestModel, mapping); | |
expect(result).toHaveProperty('test_element'); | |
expect(result.test_element).toBe(exampleTestModel.testElement); | |
}); | |
it('should map nested elements correctly', () => { | |
const result = mapModelToApi(exampleTestModel, mapping); | |
expect(result).toHaveProperty('nested_element_1'); | |
expect(result).toHaveProperty('nested_element_2'); | |
expect(result.nested_element_1).toBe( | |
exampleTestModel.nestedElement.nestedElement1 | |
); | |
expect(result.nested_element_2).toBe( | |
exampleTestModel.nestedElement.nestedElement2 | |
); | |
}); | |
}); | |
describe('API Response --> Model', () => { | |
it('should map basic elements directly', () => { | |
const result = mapApiToModel(apiResponse, mapping, TestModel); | |
expect(result).toHaveProperty('testElement'); | |
expect(result.testElement).toBe(apiResponse.test_element); | |
}); | |
it('should map flat properties to nested elements', () => { | |
const result = mapApiToModel(apiResponse, mapping, TestModel); | |
expect(result).toHaveProperty('nestedElement'); | |
expect(result.nestedElement).toHaveProperty('nestedElement1'); | |
expect(result.nestedElement).toHaveProperty('nestedElement2'); | |
expect(result.nestedElement.nestedElement1).toBe( | |
apiResponse.nested_element_1 | |
); | |
expect(result.nestedElement.nestedElement2).toBe( | |
apiResponse.nested_element_2 | |
); | |
}); | |
it('should return an object of the correct prototype', () => { | |
const result = mapApiToModel(apiResponse, mapping, TestModel); | |
expect(result).toBeInstanceOf(TestModel); | |
}); | |
}); | |
}); |
Great job, man. ;)
Thanks @pedrogquirino, much appreciated. There's a little more detail over on the repo page too, if you're interested: https://github.com/jlyman/simple-model-mapper
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I found myself needing to consistently translate roughly equivalent models from and to API responses into Javascript, and needed a way to do so reliably and repeatedly. These small utility functions are the result.