234 lines
4.5 KiB
JavaScript
234 lines
4.5 KiB
JavaScript
'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');
|
|
}
|