You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

234 lines
4.5 KiB

'use strict';
var eachProps = require('each-props');
var isPlainObject = require('is-plain-object').isPlainObject;
module.exports = function(src, dst, fromto, converter, reverse) {
if (!isObject(src)) {
src = {};
}
if (!isObject(dst)) {
dst = {};
}
if (isPlainObject(fromto)) {
fromto = onlyValueIsString(fromto);
} else if (Array.isArray(fromto)) {
fromto = arrayToObject(fromto);
} else if (typeof fromto === 'boolean') {
reverse = fromto;
converter = noop;
fromto = null;
} else if (typeof fromto === 'function') {
reverse = converter;
converter = fromto;
fromto = null;
} else {
fromto = null;
}
if (typeof converter !== 'function') {
if (typeof converter === 'boolean') {
reverse = converter;
converter = noop;
} else {
converter = noop;
}
}
if (typeof reverse !== 'boolean') {
reverse = false;
}
if (reverse) {
var tmp = src;
src = dst;
dst = tmp;
if (fromto) {
fromto = invert(fromto);
}
}
var opts = {
dest: dst,
fromto: fromto,
convert: converter,
};
if (fromto) {
eachProps(src, copyWithFromto, opts);
setParentEmptyObject(dst, fromto);
} else {
eachProps(src, copyWithoutFromto, opts);
}
return dst;
};
function copyWithFromto(value, keyChain, nodeInfo) {
if (isPlainObject(value)) {
return;
}
var dstKeyChains = nodeInfo.fromto[keyChain];
if (!dstKeyChains) {
return;
}
delete nodeInfo.fromto[keyChain];
if (!Array.isArray(dstKeyChains)) {
dstKeyChains = [dstKeyChains];
}
var srcInfo = {
keyChain: keyChain,
value: value,
key: nodeInfo.name,
depth: nodeInfo.depth,
parent: nodeInfo.parent,
};
for (var i = 0, n = dstKeyChains.length; i < n; i++) {
setDeep(nodeInfo.dest, dstKeyChains[i], function(parent, key, depth) {
var dstInfo = {
keyChain: dstKeyChains[i],
value: parent[key],
key: key,
depth: depth,
parent: parent,
};
return nodeInfo.convert(srcInfo, dstInfo);
});
}
}
function copyWithoutFromto(value, keyChain, nodeInfo) {
if (isPlainObject(value)) {
for (var k in value) {
return;
}
setDeep(nodeInfo.dest, keyChain, newObject);
return;
}
var srcInfo = {
keyChain: keyChain,
value: value,
key: nodeInfo.name,
depth: nodeInfo.depth,
parent: nodeInfo.parent,
};
setDeep(nodeInfo.dest, keyChain, function(parent, key, depth) {
var dstInfo = {
keyChain: keyChain,
value: parent[key],
key: key,
depth: depth,
parent: parent,
};
return nodeInfo.convert(srcInfo, dstInfo);
});
}
function newObject() {
return {};
}
function noop(srcInfo) {
return srcInfo.value;
}
function onlyValueIsString(obj) {
var newObj = {};
for (var key in obj) {
var val = obj[key];
if (typeof val === 'string') {
newObj[key] = val;
}
}
return newObj;
}
function arrayToObject(arr) {
var obj = {};
for (var i = 0, n = arr.length; i < n; i++) {
var elm = arr[i];
if (typeof elm === 'string') {
obj[elm] = elm;
}
}
return obj;
}
function invert(fromto) {
var inv = {};
for (var key in fromto) {
var val = fromto[key];
if (!inv[val]) {
inv[val] = [];
}
inv[val].push(key);
}
return inv;
}
function setDeep(obj, keyChain, valueCreator) {
_setDeep(obj, keyChain.split('.'), 1, valueCreator);
}
function _setDeep(obj, keyElems, depth, valueCreator) {
var key = keyElems.shift();
if (isPossibilityOfPrototypePollution(key)) {
return;
}
if (!keyElems.length) {
var value = valueCreator(obj, key, depth);
if (value === undefined) {
return;
}
if (isPlainObject(value)) { // value is always an empty object.
if (isPlainObject(obj[key])) {
return;
}
}
obj[key] = value;
return;
}
if (!isPlainObject(obj[key])) {
obj[key] = {};
}
_setDeep(obj[key], keyElems, depth + 1, valueCreator);
}
function setParentEmptyObject(obj, fromto) {
for (var srcKeyChain in fromto) {
var dstKeyChains = fromto[srcKeyChain];
if (!Array.isArray(dstKeyChains)) {
dstKeyChains = [dstKeyChains];
}
for (var i = 0, n = dstKeyChains.length; i < n; i++) {
setDeep(obj, dstKeyChains[i], newUndefined);
}
}
}
function newUndefined() {
return undefined;
}
function isObject(v) {
return Object.prototype.toString.call(v) === '[object Object]';
}
function isPossibilityOfPrototypePollution(key) {
return (key === '__proto__' || key === 'constructor');
}