Skip to content

Commit

Permalink
[WIP] Well‑formed JSON.stringify (#681)
Browse files Browse the repository at this point in the history
[WIP] Well‑formed `JSON.stringify`
  • Loading branch information
zloirock committed Nov 6, 2019
2 parents a1bc971 + ffdeacc commit c9083ad
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 36 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Changelog
##### Unreleased
- Fixed `Math.signbit`, [#687](https://github.com/zloirock/core-js/issues/687), thanks @chicoxyzzy
- Added [well-formed `JSON.stringify`](https://github.com/tc39/proposal-well-formed-stringify), ES2019 feature, thanks [@ExE-Boss](https://github.com/ExE-Boss) and [@WebReflection](https://github.com/WebReflection) for the idea
- Fixed `Math.signbit`, [#687](https://github.com/zloirock/core-js/issues/687), thanks [@chicoxyzzy](https://github.com/chicoxyzzy)

##### 3.3.6 - 2019.11.01
- Don't detect Chakra-based Edge as Chrome in the `userAgent` parsing
Expand Down
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Promise.resolve(32).then(x => console.log(x)); // => 32
- [ECMAScript: Collections](#ecmascript-collections)
- [ECMAScript: Typed Arrays](#ecmascript-typed-arrays)
- [ECMAScript: Reflect](#ecmascript-reflect)
- [ECMAScript: JSON](#ecmascript-json)
- [ECMAScript: globalThis](#ecmascript-globalthis)
- [ECMAScript proposals](#ecmascript-proposals)
- [stage 4 proposals](#stage-4-proposals)
Expand Down Expand Up @@ -1068,7 +1069,7 @@ setTimeout(() => promise.catch(() => {}), 1e3);
```
#### ECMAScript: Symbol
Modules [`es.symbol`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.js), [`es.symbol.async-iterator`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.async-iterator.js), [`es.symbol.description`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.description.js), [`es.symbol.has-instance`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.has-instance.js), [`es.symbol.is-concat-spreadable`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.is-concat-spreadable.js), [`es.symbol.iterator`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.iterator.js), [`es.symbol.match`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.match.js), [`es.symbol.replace`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.replace.js), [`es.symbol.search`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.search.js), [`es.symbol.species`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.species.js), [`es.symbol.split`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.split.js), [`es.symbol.to-primitive`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.to-primitive.js), [`es.symbol.to-string-tag`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.to-string-tag.js), [`es.symbol.unscopables`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.symbol.unscopables.js), [`es.math.to-string-tag`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.math.to-string-tag.js), [`es.json.to-string-tag`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.json.to-string-tag.js).
Modules [`es.symbol`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.js), [`es.symbol.async-iterator`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.async-iterator.js), [`es.symbol.description`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.description.js), [`es.symbol.has-instance`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.has-instance.js), [`es.symbol.is-concat-spreadable`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.is-concat-spreadable.js), [`es.symbol.iterator`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.iterator.js), [`es.symbol.match`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.match.js), [`es.symbol.replace`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.replace.js), [`es.symbol.search`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.search.js), [`es.symbol.species`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.species.js), [`es.symbol.split`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.split.js), [`es.symbol.to-primitive`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.to-primitive.js), [`es.symbol.to-string-tag`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.to-string-tag.js), [`es.symbol.unscopables`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.symbol.unscopables.js), [`es.math.to-string-tag`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.math.to-string-tag.js).
```js
class Symbol {
constructor(description?): symbol;
Expand Down Expand Up @@ -1105,10 +1106,6 @@ class Object {
static getOwnPropertyNames(object: any): Array<string>;
propertyIsEnumerable(key: PropertyKey): boolean;
}

namespace JSON {
stringify(target: any, replacer?: Function | Array, space?: string | number): string | void;
}
```
[*CommonJS entry points:*](#commonjs-api)
```
Expand All @@ -1130,7 +1127,6 @@ core-js(-pure)/es|stable|features/symbol/for
core-js(-pure)/es|stable|features/symbol/key-for
core-js(-pure)/es|stable|features/object/get-own-property-symbols
core-js(-pure)/es|stable|features/math/to-string-tag
core-js(-pure)/es|stable|features/json/to-string-tag
```
[*Basic example*](http://goo.gl/BbvWFc):
```js
Expand Down Expand Up @@ -1594,6 +1590,26 @@ let instance = Reflect.construct(C, [20, 22]);
instance.c; // => 42
```

#### ECMAScript: JSON
Since `JSON` object is missed only in very old engines like IE7-, `core-js` does not provide a full `JSON` polyfill, however, fix already existing implementations by the current standard, for example, [well-formed `JSON.stringify`](https://github.com/tc39/proposal-well-formed-stringify). `JSON` also fixed in other modules - for example, `Symbol` polyfill fixes `JSON.stringify` for correct work with symbols.

Module [`es.json.to-string-tag`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.json.to-string-tag.js) and [`es.json.stringify`](https://github.com/zloirock/core-js/blob/v3.4.0/packages/core-js/modules/es.json.stringify.js).
```js
namespace JSON {
stringify(target: any, replacer?: Function | Array, space?: string | number): string | void;
@@toStringTag: 'JSON';
}
```
[*CommonJS entry points:*](#commonjs-api)
```js
core-js(-pure)/es|stable|features/json/stringify
core-js(-pure)/es|stable|features/json/to-string-tag
```
[*Examples*](http://es6.zloirock.ru/#log(JSON.stringify(%7B%20'%F0%A0%AE%B7'%3A%20%5B'%5CuDF06%5CuD834'%5D%20%7D))%3B%20%2F%2F%20%3D%3E%20'%7B%22%F0%A0%AE%B7%22%3A%5B%22%5C%5Cudf06%5C%5Cud834%22%5D%7D'):
```js
JSON.stringify({ '𠮷': ['\uDF06\uD834'] }); // => '{"𠮷":["\\udf06\\ud834"]}'
```

#### ECMAScript: globalThis
Module [`es.global-this`](https://github.com/zloirock/core-js/blob/v3.3.6/packages/core-js/modules/es.global-this.js).
```js
Expand Down
5 changes: 5 additions & 0 deletions packages/core-js-compat/src/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@ const data = {
firefox: '65',
safari: '12.1',
},
'es.json.stringify': {
chrome: '72',
firefox: '64',
safari: '12.1',
},
'es.json.to-string-tag': {
edge: '15',
chrome: '50',
Expand Down
3 changes: 3 additions & 0 deletions packages/core-js-compat/src/modules-by-versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@ module.exports = {
'esnext.map.upsert',
'esnext.weak-map.upsert',
],
3.4: [
'es.json.stringify',
],
};
9 changes: 6 additions & 3 deletions packages/core-js/es/json/stringify.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
require('../../modules/es.json.stringify');
var core = require('../../internals/path');
var $JSON = core.JSON || (core.JSON = { stringify: JSON.stringify });

module.exports = function stringify(it) { // eslint-disable-line no-unused-vars
return $JSON.stringify.apply($JSON, arguments);
if (!core.JSON) core.JSON = { stringify: JSON.stringify };

// eslint-disable-next-line no-unused-vars
module.exports = function stringify(it, replacer, space) {
return core.JSON.stringify.apply(null, arguments);
};
32 changes: 32 additions & 0 deletions packages/core-js/modules/es.json.stringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
var $ = require('../internals/export');
var getBuiltIn = require('../internals/get-built-in');
var fails = require('../internals/fails');

var $stringify = getBuiltIn('JSON', 'stringify');
var re = /[\uD800-\uDFFF]/g;
var low = /^[\uD800-\uDBFF]$/;
var hi = /^[\uDC00-\uDFFF]$/;

var fix = function (match, offset, string) {
var prev = string.charAt(offset - 1);
var next = string.charAt(offset + 1);
if ((low.test(match) && !hi.test(next)) || (hi.test(match) && !low.test(prev))) {
return '\\u' + match.charCodeAt(0).toString(16);
} return match;
};

var FORCED = fails(function () {
return $stringify('\uDF06\uD834') !== '"\\udf06\\ud834"'
|| $stringify('\uDEAD') !== '"\\udead"';
});

if ($stringify) {
// https://github.com/tc39/proposal-well-formed-stringify
$({ target: 'JSON', stat: true, forced: FORCED }, {
// eslint-disable-next-line no-unused-vars
stringify: function stringify(it, replacer, space) {
var result = $stringify.apply(null, arguments);
return typeof result == 'string' ? result.replace(re, fix) : result;
}
});
}
57 changes: 31 additions & 26 deletions packages/core-js/modules/es.symbol.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';
var $ = require('../internals/export');
var global = require('../internals/global');
var getBuiltIn = require('../internals/get-built-in');
var IS_PURE = require('../internals/is-pure');
var DESCRIPTORS = require('../internals/descriptors');
var NATIVE_SYMBOL = require('../internals/native-symbol');
Expand Down Expand Up @@ -42,8 +43,7 @@ var setInternalState = InternalStateModule.set;
var getInternalState = InternalStateModule.getterFor(SYMBOL);
var ObjectPrototype = Object[PROTOTYPE];
var $Symbol = global.Symbol;
var JSON = global.JSON;
var nativeJSONStringify = JSON && JSON.stringify;
var $stringify = getBuiltIn('JSON', 'stringify');
var nativeGetOwnPropertyDescriptor = getOwnPropertyDescriptorModule.f;
var nativeDefineProperty = definePropertyModule.f;
var nativeGetOwnPropertyNames = getOwnPropertyNamesExternal.f;
Expand Down Expand Up @@ -264,30 +264,35 @@ $({ target: 'Object', stat: true, forced: fails(function () { getOwnPropertySymb

// `JSON.stringify` method behavior with symbols
// https://tc39.github.io/ecma262/#sec-json.stringify
JSON && $({ target: 'JSON', stat: true, forced: !NATIVE_SYMBOL || fails(function () {
var symbol = $Symbol();
// MS Edge converts symbol values to JSON as {}
return nativeJSONStringify([symbol]) != '[null]'
// WebKit converts symbol values to JSON as null
|| nativeJSONStringify({ a: symbol }) != '{}'
// V8 throws on boxed symbols
|| nativeJSONStringify(Object(symbol)) != '{}';
}) }, {
stringify: function stringify(it) {
var args = [it];
var index = 1;
var replacer, $replacer;
while (arguments.length > index) args.push(arguments[index++]);
$replacer = replacer = args[1];
if (!isObject(replacer) && it === undefined || isSymbol(it)) return; // IE8 returns string on undefined
if (!isArray(replacer)) replacer = function (key, value) {
if (typeof $replacer == 'function') value = $replacer.call(this, key, value);
if (!isSymbol(value)) return value;
};
args[1] = replacer;
return nativeJSONStringify.apply(JSON, args);
}
});
if ($stringify) {
var FORCED_JSON_STRINGIFY = !NATIVE_SYMBOL || fails(function () {
var symbol = $Symbol();
// MS Edge converts symbol values to JSON as {}
return $stringify([symbol]) != '[null]'
// WebKit converts symbol values to JSON as null
|| $stringify({ a: symbol }) != '{}'
// V8 throws on boxed symbols
|| $stringify(Object(symbol)) != '{}';
});

$({ target: 'JSON', stat: true, forced: FORCED_JSON_STRINGIFY }, {
// eslint-disable-next-line no-unused-vars
stringify: function stringify(it, replacer, space) {
var args = [it];
var index = 1;
var $replacer;
while (arguments.length > index) args.push(arguments[index++]);
$replacer = replacer;
if (!isObject(replacer) && it === undefined || isSymbol(it)) return; // IE8 returns string on undefined
if (!isArray(replacer)) replacer = function (key, value) {
if (typeof $replacer == 'function') value = $replacer.call(this, key, value);
if (!isSymbol(value)) return value;
};
args[1] = replacer;
return $stringify.apply(null, args);
}
});
}

// `Symbol.prototype[@@toPrimitive]` method
// https://tc39.github.io/ecma262/#sec-symbol.prototype-@@toprimitive
Expand Down
4 changes: 4 additions & 0 deletions tests/compat/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@ GLOBAL.tests = {
'es.global-this': function () {
return globalThis;
},
'es.json.stringify': function () {
return JSON.stringify('\uDF06\uD834') === '"\\udf06\\ud834"'
&& JSON.stringify('\uDEAD') === '"\\udead"';
},
'es.json.to-string-tag': [SYMBOLS_SUPPORT, function () {
return JSON[Symbol.toStringTag];
}],
Expand Down
20 changes: 20 additions & 0 deletions tests/pure/es.json.stringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { GLOBAL } from '../helpers/constants';
import stringify from 'core-js/es/json/stringify';

if (GLOBAL.JSON) {
QUnit.test('Well‑formed JSON.stringify', assert => {
assert.isFunction(stringify);
assert.arity(stringify, 3);
assert.name(stringify, 'stringify');

assert.same(stringify({ foo: 'bar' }), '{"foo":"bar"}', 'basic');
assert.same(stringify('\uDEAD'), '"\\udead"', 'r1');
assert.same(stringify('\uDF06\uD834'), '"\\udf06\\ud834"', 'r2');
assert.same(stringify('\uDF06ab\uD834'), '"\\udf06ab\\ud834"', 'r3');
assert.same(stringify('𠮷'), '"𠮷"', 'r4');
assert.same(stringify('\uD834\uDF06'), '"𝌆"', 'r5');
assert.same(stringify('\uD834\uD834\uDF06'), '"\\ud834𝌆"', 'r6');
assert.same(stringify('\uD834\uDF06\uDF06'), '"𝌆\\udf06"', 'r7');
assert.same(stringify({ '𠮷': ['\uDF06\uD834'] }), '{"𠮷":["\\udf06\\ud834"]}', 'r8');
});
}
1 change: 1 addition & 0 deletions tests/pure/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import './es.date.to-json';
import './es.function.bind';
import './es.function.has-instance';
import './es.global-this';
import './es.json.stringify';
import './es.map';
import './es.math.acosh';
import './es.math.asinh';
Expand Down
21 changes: 21 additions & 0 deletions tests/tests/es.json.stringify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { GLOBAL } from '../helpers/constants';

if (GLOBAL.JSON) {
QUnit.test('Well‑formed JSON.stringify', assert => {
const { stringify } = JSON;
assert.isFunction(stringify);
assert.arity(stringify, 3);
assert.name(stringify, 'stringify');
assert.looksNative(stringify);

assert.same(stringify({ foo: 'bar' }), '{"foo":"bar"}', 'basic');
assert.same(stringify('\uDEAD'), '"\\udead"', 'r1');
assert.same(stringify('\uDF06\uD834'), '"\\udf06\\ud834"', 'r2');
assert.same(stringify('\uDF06ab\uD834'), '"\\udf06ab\\ud834"', 'r3');
assert.same(stringify('𠮷'), '"𠮷"', 'r4');
assert.same(stringify('\uD834\uDF06'), '"𝌆"', 'r5');
assert.same(stringify('\uD834\uD834\uDF06'), '"\\ud834𝌆"', 'r6');
assert.same(stringify('\uD834\uDF06\uDF06'), '"𝌆\\udf06"', 'r7');
assert.same(stringify({ '𠮷': ['\uDF06\uD834'] }), '{"𠮷":["\\udf06\\ud834"]}', 'r8');
});
}
1 change: 1 addition & 0 deletions tests/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import './es.function.bind';
import './es.function.has-instance';
import './es.function.name';
import './es.global-this';
import './es.json.stringify';
import './es.map';
import './es.math.acosh';
import './es.math.asinh';
Expand Down

0 comments on commit c9083ad

Please sign in to comment.