| 474 | // object that is already on the "stack" of items being processed. Accepts an optional replacer |
| 475 | |
| 476 | function canonicalize(obj, stack, replacementStack, replacer, key) { |
| 477 | stack = stack || []; |
| 478 | replacementStack = replacementStack || []; |
| 479 | |
| 480 | if (replacer) { |
| 481 | obj = replacer(key, obj); |
| 482 | } |
| 483 | |
| 484 | var i; |
| 485 | |
| 486 | for (i = 0; i < stack.length; i += 1) { |
| 487 | if (stack[i] === obj) { |
| 488 | return replacementStack[i]; |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | var canonicalizedObj; |
| 493 | |
| 494 | if ('[object Array]' === objectPrototypeToString.call(obj)) { |
| 495 | stack.push(obj); |
| 496 | canonicalizedObj = new Array(obj.length); |
| 497 | replacementStack.push(canonicalizedObj); |
| 498 | |
| 499 | for (i = 0; i < obj.length; i += 1) { |
| 500 | canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); |
| 501 | } |
| 502 | |
| 503 | stack.pop(); |
| 504 | replacementStack.pop(); |
| 505 | return canonicalizedObj; |
| 506 | } |
| 507 | |
| 508 | if (obj && obj.toJSON) { |
| 509 | obj = obj.toJSON(); |
| 510 | } |
| 511 | |
| 512 | if (_typeof(obj) === 'object' && obj !== null) { |
| 513 | stack.push(obj); |
| 514 | canonicalizedObj = {}; |
| 515 | replacementStack.push(canonicalizedObj); |
| 516 | |
| 517 | var sortedKeys = [], |
| 518 | _key; |
| 519 | |
| 520 | for (_key in obj) { |
| 521 | /* istanbul ignore else */ |
| 522 | if (obj.hasOwnProperty(_key)) { |
| 523 | sortedKeys.push(_key); |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | sortedKeys.sort(); |
| 528 | |
| 529 | for (i = 0; i < sortedKeys.length; i += 1) { |
| 530 | _key = sortedKeys[i]; |
| 531 | canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); |
| 532 | } |
| 533 | |