es.json.stringify.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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 lowSurrogates = /^[\uD800-\uDBFF]$/;
  30. var hiSurrogates = /^[\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 ((exec(lowSurrogates, match) && !exec(hiSurrogates, next)) || (exec(hiSurrogates, match) && !exec(lowSurrogates, prev))) {
  62. return '\\u' + numberToString(charCodeAt(match, 0), 16);
  63. } return match;
  64. };
  65. var getReplacerFunction = function (replacer) {
  66. if (isCallable(replacer)) return replacer;
  67. if (!isArray(replacer)) return;
  68. var rawLength = replacer.length;
  69. var keys = [];
  70. for (var i = 0; i < rawLength; i++) {
  71. var element = replacer[i];
  72. if (typeof element == 'string') push(keys, element);
  73. else if (typeof element == 'number' || classof(element) === 'Number' || classof(element) === 'String') push(keys, toString(element));
  74. }
  75. var keysLength = keys.length;
  76. var root = true;
  77. return function (key, value) {
  78. if (root) {
  79. root = false;
  80. return value;
  81. }
  82. if (isArray(this)) return value;
  83. for (var j = 0; j < keysLength; j++) if (keys[j] === key) return value;
  84. };
  85. };
  86. // `JSON.stringify` method
  87. // https://tc39.es/ecma262/#sec-json.stringify
  88. // https://github.com/tc39/proposal-json-parse-with-source
  89. if ($stringify) $({ target: 'JSON', stat: true, arity: 3, forced: WRONG_SYMBOLS_CONVERSION || ILL_FORMED_UNICODE || !NATIVE_RAW_JSON }, {
  90. stringify: function stringify(text, replacer, space) {
  91. var replacerFunction = getReplacerFunction(replacer);
  92. var rawStrings = [];
  93. var json = stringifyWithProperSymbolsConversion(text, function (key, value) {
  94. // some old implementations (like WebKit) could pass numbers as keys
  95. var v = isCallable(replacerFunction) ? call(replacerFunction, this, $String(key), value) : value;
  96. return !NATIVE_RAW_JSON && isRawJSON(v) ? MARK + (push(rawStrings, v.rawJSON) - 1) : v;
  97. }, space);
  98. if (typeof json != 'string') return json;
  99. if (ILL_FORMED_UNICODE) json = replace(json, surrogates, fixIllFormedJSON);
  100. if (NATIVE_RAW_JSON) return json;
  101. var result = '';
  102. var length = json.length;
  103. for (var i = 0; i < length; i++) {
  104. var chr = charAt(json, i);
  105. if (chr === '"') {
  106. var end = parseJSONString(json, ++i).end - 1;
  107. var string = slice(json, i, end);
  108. result += slice(string, 0, MARK_LENGTH) === MARK
  109. ? rawStrings[slice(string, MARK_LENGTH)]
  110. : '"' + string + '"';
  111. i = end;
  112. } else result += chr;
  113. }
  114. return result;
  115. }
  116. });