index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. 'use strict';
  2. var eachProps = require('each-props');
  3. var isPlainObject = require('is-plain-object').isPlainObject;
  4. module.exports = function(src, dst, fromto, converter, reverse) {
  5. if (!isObject(src)) {
  6. src = {};
  7. }
  8. if (!isObject(dst)) {
  9. dst = {};
  10. }
  11. if (isPlainObject(fromto)) {
  12. fromto = onlyValueIsString(fromto);
  13. } else if (Array.isArray(fromto)) {
  14. fromto = arrayToObject(fromto);
  15. } else if (typeof fromto === 'boolean') {
  16. reverse = fromto;
  17. converter = noop;
  18. fromto = null;
  19. } else if (typeof fromto === 'function') {
  20. reverse = converter;
  21. converter = fromto;
  22. fromto = null;
  23. } else {
  24. fromto = null;
  25. }
  26. if (typeof converter !== 'function') {
  27. if (typeof converter === 'boolean') {
  28. reverse = converter;
  29. converter = noop;
  30. } else {
  31. converter = noop;
  32. }
  33. }
  34. if (typeof reverse !== 'boolean') {
  35. reverse = false;
  36. }
  37. if (reverse) {
  38. var tmp = src;
  39. src = dst;
  40. dst = tmp;
  41. if (fromto) {
  42. fromto = invert(fromto);
  43. }
  44. }
  45. var opts = {
  46. dest: dst,
  47. fromto: fromto,
  48. convert: converter,
  49. };
  50. if (fromto) {
  51. eachProps(src, copyWithFromto, opts);
  52. setParentEmptyObject(dst, fromto);
  53. } else {
  54. eachProps(src, copyWithoutFromto, opts);
  55. }
  56. return dst;
  57. };
  58. function copyWithFromto(value, keyChain, nodeInfo) {
  59. if (isPlainObject(value)) {
  60. return;
  61. }
  62. var dstKeyChains = nodeInfo.fromto[keyChain];
  63. if (!dstKeyChains) {
  64. return;
  65. }
  66. delete nodeInfo.fromto[keyChain];
  67. if (!Array.isArray(dstKeyChains)) {
  68. dstKeyChains = [dstKeyChains];
  69. }
  70. var srcInfo = {
  71. keyChain: keyChain,
  72. value: value,
  73. key: nodeInfo.name,
  74. depth: nodeInfo.depth,
  75. parent: nodeInfo.parent,
  76. };
  77. for (var i = 0, n = dstKeyChains.length; i < n; i++) {
  78. setDeep(nodeInfo.dest, dstKeyChains[i], function(parent, key, depth) {
  79. var dstInfo = {
  80. keyChain: dstKeyChains[i],
  81. value: parent[key],
  82. key: key,
  83. depth: depth,
  84. parent: parent,
  85. };
  86. return nodeInfo.convert(srcInfo, dstInfo);
  87. });
  88. }
  89. }
  90. function copyWithoutFromto(value, keyChain, nodeInfo) {
  91. if (isPlainObject(value)) {
  92. for (var k in value) {
  93. return;
  94. }
  95. setDeep(nodeInfo.dest, keyChain, newObject);
  96. return;
  97. }
  98. var srcInfo = {
  99. keyChain: keyChain,
  100. value: value,
  101. key: nodeInfo.name,
  102. depth: nodeInfo.depth,
  103. parent: nodeInfo.parent,
  104. };
  105. setDeep(nodeInfo.dest, keyChain, function(parent, key, depth) {
  106. var dstInfo = {
  107. keyChain: keyChain,
  108. value: parent[key],
  109. key: key,
  110. depth: depth,
  111. parent: parent,
  112. };
  113. return nodeInfo.convert(srcInfo, dstInfo);
  114. });
  115. }
  116. function newObject() {
  117. return {};
  118. }
  119. function noop(srcInfo) {
  120. return srcInfo.value;
  121. }
  122. function onlyValueIsString(obj) {
  123. var newObj = {};
  124. for (var key in obj) {
  125. var val = obj[key];
  126. if (typeof val === 'string') {
  127. newObj[key] = val;
  128. }
  129. }
  130. return newObj;
  131. }
  132. function arrayToObject(arr) {
  133. var obj = {};
  134. for (var i = 0, n = arr.length; i < n; i++) {
  135. var elm = arr[i];
  136. if (typeof elm === 'string') {
  137. obj[elm] = elm;
  138. }
  139. }
  140. return obj;
  141. }
  142. function invert(fromto) {
  143. var inv = {};
  144. for (var key in fromto) {
  145. var val = fromto[key];
  146. if (!inv[val]) {
  147. inv[val] = [];
  148. }
  149. inv[val].push(key);
  150. }
  151. return inv;
  152. }
  153. function setDeep(obj, keyChain, valueCreator) {
  154. _setDeep(obj, keyChain.split('.'), 1, valueCreator);
  155. }
  156. function _setDeep(obj, keyElems, depth, valueCreator) {
  157. var key = keyElems.shift();
  158. if (isPossibilityOfPrototypePollution(key)) {
  159. return;
  160. }
  161. if (!keyElems.length) {
  162. var value = valueCreator(obj, key, depth);
  163. if (value === undefined) {
  164. return;
  165. }
  166. if (isPlainObject(value)) { // value is always an empty object.
  167. if (isPlainObject(obj[key])) {
  168. return;
  169. }
  170. }
  171. obj[key] = value;
  172. return;
  173. }
  174. if (!isPlainObject(obj[key])) {
  175. obj[key] = {};
  176. }
  177. _setDeep(obj[key], keyElems, depth + 1, valueCreator);
  178. }
  179. function setParentEmptyObject(obj, fromto) {
  180. for (var srcKeyChain in fromto) {
  181. var dstKeyChains = fromto[srcKeyChain];
  182. if (!Array.isArray(dstKeyChains)) {
  183. dstKeyChains = [dstKeyChains];
  184. }
  185. for (var i = 0, n = dstKeyChains.length; i < n; i++) {
  186. setDeep(obj, dstKeyChains[i], newUndefined);
  187. }
  188. }
  189. }
  190. function newUndefined() {
  191. return undefined;
  192. }
  193. function isObject(v) {
  194. return Object.prototype.toString.call(v) === '[object Object]';
  195. }
  196. function isPossibilityOfPrototypePollution(key) {
  197. return (key === '__proto__' || key === 'constructor');
  198. }