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
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'); |
|
}
|
|
|