es.json.stringify.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. 'use strict';
  2. var $ = require('../internals/export');
  3. var getBuiltIn = require('../internals/get-built-in');
  4. var apply = require('../internals/function-apply');
  5. var call = require('../internals/function-call');
  6. var uncurryThis = require('../internals/function-uncurry-this');
  7. var fails = require('../internals/fails');
  8. var isArray = require('../internals/is-array');
  9. var isCallable = require('../internals/is-callable');
  10. var isRawJSON = require('../internals/is-raw-json');
  11. var isSymbol = require('../internals/is-symbol');
  12. var classof = require('../internals/classof-raw');
  13. var toString = require('../internals/to-string');
  14. var arraySlice = require('../internals/array-slice');
  15. var parseJSONString = require('../internals/parse-json-string');
  16. var uid = require('../internals/uid');
  17. var NATIVE_SYMBOL = require('../internals/symbol-constructor-detection');
  18. var NATIVE_RAW_JSON = require('../internals/native-raw-json');
  19. var $String = String;
  20. var $stringify = getBuiltIn('JSON', 'stringify');
  21. var exec = uncurryThis(/./.exec);
  22. var charAt = uncurryThis(''.charAt);
  23. var charCodeAt = uncurryThis(''.charCodeAt);
  24. var replace = uncurryThis(''.replace);
  25. var slice = uncurryThis(''.slice);
  26. var push = uncurryThis([].push);
  27. var numberToString = uncurryThis(1.1.toString);
  28. var surrogates = /[\uD800-\uDFFF]/g;
  29. var leadingSurrogates = /^[\uD800-\uDBFF]$/;
  30. var trailingSurrogates = /^[\uDC00-\uDFFF]$/;
  31. var MARK = uid();
  32. var MARK_LENGTH = MARK.length;
  33. var WRONG_SYMBOLS_CONVERSION = !NATIVE_SYMBOL || fails(function () {
  34. var symbol = getBuiltIn('Symbol')('stringify detection');
  35. // MS Edge converts symbol values to JSON as {}
  36. return $stringify([symbol]) !== '[null]'
  37. // WebKit converts symbol values to JSON as null
  38. || $stringify({ a: symbol }) !== '{}'
  39. // V8 throws on boxed symbols
  40. || $stringify(Object(symbol)) !== '{}';
  41. });
  42. // https://github.com/tc39/proposal-well-formed-stringify
  43. var ILL_FORMED_UNICODE = fails(function () {
  44. return $stringify('\uDF06\uD834') !== '"\\udf06\\ud834"'
  45. || $stringify('\uDEAD') !== '"\\udead"';
  46. });
  47. var stringifyWithProperSymbolsConversion = WRONG_SYMBOLS_CONVERSION ? function (it, replacer) {
  48. var args = arraySlice(arguments);
  49. var $replacer = getReplacerFunction(replacer);
  50. if (!isCallable($replacer) && (it === undefined || isSymbol(it))) return; // IE8 returns string on undefined
  51. args[1] = function (key, value) {
  52. // some old implementations (like WebKit) could pass numbers as keys
  53. if (isCallable($replacer)) value = call($replacer, this, $String(key), value);
  54. if (!isSymbol(value)) return value;
  55. };
  56. return apply($stringify, null, args);
  57. } : $stringify;
  58. var fixIllFormedJSON = function (match, offset, string) {
  59. var prev = charAt(string, offset - 1);
  60. var next = charAt(string, offset + 1);
  61. if (
  62. (exec(leadingSurrogates, match) && !exec(trailingSurrogates, next)) ||
  63. (exec(trailingSurrogates, match) && !exec(leadingSurrogates, prev))
  64. ) {
  65. return '\\u' + numberToString(charCodeAt(match, 0), 16);
  66. } return match;
  67. };
  68. var getReplacerFunction = function (replacer) {
  69. if (isCallable(replacer)) return replacer;
  70. if (!isArray(replacer)) return;
  71. var rawLength = replacer.length;
  72. var keys = [];
  73. for (var i = 0; i < rawLength; i++) {
  74. var element = replacer[i];
  75. if (typeof element == 'string') push(keys, element);
  76. else if (typeof element == 'number' || classof(element) === 'Number' || classof(element) === 'String') push(keys, toString(element));
  77. }
  78. var keysLength = keys.length;
  79. var root = true;
  80. return function (key, value) {
  81. if (root) {
  82. root = false;
  83. return value;
  84. }
  85. if (isArray(this)) return value;
  86. for (var j = 0; j < keysLength; j++) if (keys[j] === key) return value;
  87. };
  88. };
  89. // `JSON.stringify` method
  90. // https://tc39.es/ecma262/#sec-json.stringify
  91. // https://github.com/tc39/proposal-json-parse-with-source
  92. if ($stringify) $({ target: 'JSON', stat: true, arity: 3, forced: WRONG_SYMBOLS_CONVERSION || ILL_FORMED_UNICODE || !NATIVE_RAW_JSON }, {
  93. stringify: function stringify(text, replacer, space) {
  94. var replacerFunction = getReplacerFunction(replacer);
  95. var rawStrings = [];
  96. var json = stringifyWithProperSymbolsConversion(text, function (key, value) {
  97. // some old implementations (like WebKit) could pass numbers as keys
  98. var v = isCallable(replacerFunction) ? call(replacerFunction, this, $String(key), value) : value;
  99. return !NATIVE_RAW_JSON && isRawJSON(v) ? MARK + (push(rawStrings, v.rawJSON) - 1) : v;
  100. }, space);
  101. if (typeof json != 'string') return json;
  102. if (ILL_FORMED_UNICODE) json = replace(json, surrogates, fixIllFormedJSON);
  103. if (NATIVE_RAW_JSON) return json;
  104. var result = '';
  105. var length = json.length;
  106. for (var i = 0; i < length; i++) {
  107. var chr = charAt(json, i);
  108. if (chr === '"') {
  109. var end = parseJSONString(json, ++i).end - 1;
  110. var string = slice(json, i, end);
  111. result += slice(string, 0, MARK_LENGTH) === MARK
  112. ? rawStrings[slice(string, MARK_LENGTH)]
  113. : '"' + string + '"';
  114. i = end;
  115. } else result += chr;
  116. }
  117. return result;
  118. }
  119. });