@ericblade/mws-advanced
Version:
Modern, fully featured, very opinionated node javascript Amazon MWS API based on @ericblade/mws-simple
224 lines (202 loc) • 6.84 kB
JavaScript
/**
* @module transformers
* More than meets the eye.
*/
/**
* Force parameter to be an array. If it's already an array, return a copy of the original.
* If it's not an array, then return it inside an array. Useful for ensuring that things that
* should always be arrays, are actually always arrays.
*
* @private
* @param {any} arr - item to force to array
* @return {array} - copy of array, or an array containing arr
*/
const forceArray = (arr) => [].concat(arr !== undefined ? arr : []);
/**
* Test if a string is all upper case. Not a transformer, but used in the key transformer
*
* @private
* @param {string} str string to test
* @return {boolean} true if string is all UPPERCASE
*/
const isUpperCase = (str) => typeof str === 'string' && str === str.toUpperCase();
// https://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
/**
* Attempt to relatively intelligently convert any string to camelCase (CamelCase => camelCase)
*
* @private
* @param {string} - string to convert
* @returns {string} - camelCase version of string
*/
const camelize = (str) => {
const ret = str.replace(
/(?:^\w|[A-Z]|\b\w)/g,
(letter, index) => (index === 0 ? letter.toLowerCase() : letter.toUpperCase()),
).replace(/\s+/g, '');
return ret;
};
/**
* Move all contents of a subobject 'key' down one level
*
* @private
* @param {string} key - key to move
* @param {object} obj - object to move in
* @return {object} new object with sub-object contents of 'key' moved one level lower
*/
const subObjUpLevel = (key, obj) => {
const ret = {
...obj,
...obj[key],
};
delete ret[key];
return ret;
};
// This is a version of subObjUpLevel that fails tests --
// somehow, on objects that had key, it would work, but on objects that didn't contain
// key, it would create key as undefined. It's not clear why it would effectively delete
// key if it had already existed, because it should be setting it to undefined. So, I think there
// may be either a node bug, or something i'm not familiar with in the spec. I'm leaving this
// here right now for future investigation, I should write a test case for the most basic
// version and query some devs to see if it's a bug.
// const subObjUpLevel = (key, obj) => ({
// ...obj,
// ...obj[key],
// [key]: undefined,
// });
/**
* Rename obj._ to obj.value, and move contents of obj.$ one level lower
*
* @private
* @param {object} obj - object to operate on
* @return {object} transformed object
*/
const objToValueSub = (obj) => ({
value: obj._,
...obj.$,
});
const transformKey = (k) => (isUpperCase(k) ? k : camelize(k));
/**
* remove a string pattern from a string
*
* @param {string} str string to remove pattern from
* @param {string|regex} pattern pattern to remove
* @return {string} string with the pattern removed
*/
const removeFromString = (str, pattern) => str.replace(pattern, '');
/**
* Special key transformer for transformObjectKeys to strip "ns2:" from the beginning of keys,
* such as 'ns2:ItemAttributes'
*
* @param {any} k
*/
const transformAttributeSetKey = (k) => transformKey(removeFromString(k, /^ns2:/));
/**
* Transforms an object, and it's keys. Use keyTransformer as a special key transformer function.
*
* This is a rather complex function, which takes unintuitive JSON from XML conversion, and attempts
* to organize it in a more understandable fashion. It will convert internal objects such as:
* { "_": "4.20", "$": { "Units": "inches" } }
* to
* { "inches": "4.20" }
*
* With the application of keyTransformer, you can also convert something like
* { "ns2:ManufacturerMinimumAge": { "_": "36", "$": { "Units": "months" } } }
* to a much more readable:
* { "manufacturerMinimumAge: { "months": "36" } },
*
* @param {any} obj
* @param {any} [keyTransformer=transformKey]
* @returns transformed object
*/
/* eslint-disable no-param-reassign */
const transformObjectKeys = (obj, keyTransformer = transformKey) => {
if (!obj) return obj;
if (typeof obj.$ === 'object') {
if (obj._) {
obj = objToValueSub(obj);
} else {
obj = subObjUpLevel('$', obj);
}
}
if (Array.isArray(obj)) {
// map objects in array through this function, otherwise simply return the item
return obj.map(((x) => (typeof x === 'object' ? transformObjectKeys(x, keyTransformer) : x)));
}
const ret = Object.keys(obj).reduce((r, key) => {
const destKey = keyTransformer(key);
if (typeof obj[key] === 'object') {
r[destKey] = transformObjectKeys(obj[key], keyTransformer);
} else {
r[destKey] = obj[key];
}
return r;
}, {});
return ret;
};
/* eslint-enable no-param-reassign */
const stringToBool = (str) => (
str === 'true' ? true : str === 'false' ? false : str // eslint-disable-line no-nested-ternary
);
const stringToFloat = (str) => {
const i = parseFloat(str);
return str == i ? i : str; // eslint-disable-line eqeqeq
};
const stringToInt = (str) => {
const i = parseFloat(str);
return str >>> 0 === i ? i : str; // eslint-disable-line no-bitwise
};
const stringToPrimitives = (str) => (
stringToBool(stringToFloat(stringToInt(str)))
);
const transformObjectToPrimitives = (obj) => (
Object.keys(obj).reduce((acc, key) => {
acc[key] = stringToPrimitives(obj[key]);
return acc;
}, {})
);
/**
* Returns an object indexed by the given property name,
* with that property removed, otherwise identical to the original
*/
const objIndexedBy = (obj, name) => {
const out = { [obj[name]]: { ...obj } };
delete out[obj[name]][name];
return out;
};
/**
* Reduce an Array into a single Object, with each 'name' key of the array objects
* becoming the index in the array. ie,
* [ {asin: '12345', data: { ... }}, { asin: 'abcde', data: { ... } } ]
* becomes
* { 12345: { data }, abcde: { data } }
*/
const reduceArrayIntoObjBy = (arr, obj, name) => (
arr.reduce((acc, next) => {
acc[next[name]] = { ...next };
delete acc[next[name]][name];
return acc;
}, obj)
);
module.exports = {
forceArray, // not public, exported for other modules inside this lib
objIndexedBy,
reduceArrayIntoObjBy,
transformObjectKeys,
transformKey, // not public, but exported for other modules inside this lib to use it
transformAttributeSetKey,
transformObjectToPrimitives,
stringToBool,
stringToFloat,
stringToInt,
stringToPrimitives,
};
if (process.env.NODE_ENV === 'testing') {
module.exports = {
...module.exports,
camelize,
isUpperCase,
objToValueSub,
removeFromString,
subObjUpLevel,
};
}