[ES6] JSON.stringify should ignore object properties that have symbol values and...
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Aug 2015 05:53:37 +0000 (05:53 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 31 Aug 2015 05:53:37 +0000 (05:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=148628

Reviewed by Saam Barati.

As per ECMA262 6.0,

1. JSON.stringify should ignore object properties that have symbol values.

    SerializeJSONProperty[1] will return undefined if the value of the property is a symbol.
    In this case, SerializeJSONObject[2] does not append any string for this property.

2. JSON.stringify should convert the symbol values in array to null

    As the same to the object case, SerializeJSONProperty will return undefined if the value of the property is a symbol.
    But in the case of arrays, if the result of SerializeJSONProperty is undefined, it will emit "null"[3].
    This behavior is already implemented in the existing JSON.stringify. Added tests to ensure that.

[1]: http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonproperty
[2]: http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonobject
[3]: http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonarray

* runtime/JSONObject.cpp:
(JSC::unwrapBoxedPrimitive):
(JSC::Stringifier::appendStringifiedValue):
(JSC::Stringifier::Holder::appendNextProperty):
* tests/stress/symbol-with-json.js:
(shouldBe):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@189162 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/runtime/JSONObject.cpp
Source/JavaScriptCore/tests/stress/symbol-with-json.js

index cf40bd3..d6b8cc5 100644 (file)
@@ -1,3 +1,34 @@
+2015-08-30  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [ES6] JSON.stringify should ignore object properties that have symbol values and convert the symbol values in array to null
+        https://bugs.webkit.org/show_bug.cgi?id=148628
+
+        Reviewed by Saam Barati.
+
+        As per ECMA262 6.0,
+
+        1. JSON.stringify should ignore object properties that have symbol values.
+
+            SerializeJSONProperty[1] will return undefined if the value of the property is a symbol.
+            In this case, SerializeJSONObject[2] does not append any string for this property.
+
+        2. JSON.stringify should convert the symbol values in array to null
+
+            As the same to the object case, SerializeJSONProperty will return undefined if the value of the property is a symbol.
+            But in the case of arrays, if the result of SerializeJSONProperty is undefined, it will emit "null"[3].
+            This behavior is already implemented in the existing JSON.stringify. Added tests to ensure that.
+
+        [1]: http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonproperty
+        [2]: http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonobject
+        [3]: http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonarray
+
+        * runtime/JSONObject.cpp:
+        (JSC::unwrapBoxedPrimitive):
+        (JSC::Stringifier::appendStringifiedValue):
+        (JSC::Stringifier::Holder::appendNextProperty):
+        * tests/stress/symbol-with-json.js:
+        (shouldBe):
+
 2015-08-30  Filip Pizlo  <fpizlo@apple.com>
 
         JSC property attributes should fit in a byte
index f67ba7e..4a47f18 100644 (file)
@@ -110,7 +110,7 @@ private:
     JSValue toJSON(JSValue, const PropertyNameForFunctionCall&);
     JSValue toJSONImpl(JSValue, const PropertyNameForFunctionCall&);
 
-    enum StringifyResult { StringifyFailed, StringifySucceeded, StringifyFailedDueToUndefinedValue };
+    enum StringifyResult { StringifyFailed, StringifySucceeded, StringifyFailedDueToUndefinedOrSymbolValue };
     StringifyResult appendStringifiedValue(StringBuilder&, JSValue, JSObject* holder, const PropertyNameForFunctionCall&);
 
     bool willIndent() const;
@@ -144,6 +144,9 @@ static inline JSValue unwrapBoxedPrimitive(ExecState* exec, JSValue value)
         return object->toString(exec);
     if (object->inherits(BooleanObject::info()))
         return object->toPrimitive(exec);
+
+    // Do not unwrap SymbolObject to Symbol. It is not performed in the spec.
+    // http://www.ecma-international.org/ecma-262/6.0/#sec-serializejsonproperty
     return value;
 }
 
@@ -299,8 +302,8 @@ Stringifier::StringifyResult Stringifier::appendStringifiedValue(StringBuilder&
             return StringifyFailed;
     }
 
-    if (value.isUndefined() && !holder->inherits(JSArray::info()))
-        return StringifyFailedDueToUndefinedValue;
+    if ((value.isUndefined() || value.isSymbol()) && !holder->inherits(JSArray::info()))
+        return StringifyFailedDueToUndefinedOrSymbolValue;
 
     if (value.isNull()) {
         builder.appendLiteral("null");
@@ -349,7 +352,7 @@ Stringifier::StringifyResult Stringifier::appendStringifiedValue(StringBuilder&
             builder.appendLiteral("null");
             return StringifySucceeded;
         }
-        return StringifyFailedDueToUndefinedValue;
+        return StringifyFailedDueToUndefinedOrSymbolValue;
     }
 
     // Handle cycle detection, and put the holder on the stack.
@@ -514,10 +517,10 @@ bool Stringifier::Holder::appendNextProperty(Stringifier& stringifier, StringBui
             break;
         case StringifySucceeded:
             break;
-        case StringifyFailedDueToUndefinedValue:
-            // This only occurs when get an undefined value for an object property.
-            // In this case we don't want the separator and property name that we
-            // already appended, so roll back.
+        case StringifyFailedDueToUndefinedOrSymbolValue:
+            // This only occurs when get an undefined value or a symbol value for
+            // an object property. In this case we don't want the separator and
+            // property name that we already appended, so roll back.
             builder.resize(rollBackPoint);
             break;
     }
index 3c03d5d..d9127b3 100644 (file)
@@ -1,14 +1,29 @@
 // This tests JSON correctly behaves with Symbol.
 
-if (JSON.stringify(Symbol('Cocoa')) !== undefined)
-    throw "Error: bad value " + JSON.stringify(Symbol('Cocoa'));
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+shouldBe(JSON.stringify(Symbol('Cocoa')), undefined);
 
 var object = {};
 var symbol = Symbol("Cocoa");
 object[symbol] = 42;
 object['Cappuccino'] = 42;
-if (JSON.stringify(object) !== '{"Cappuccino":42}')
-    throw "Error: bad value " + JSON.stringify(object);
+shouldBe(JSON.stringify(object), '{"Cappuccino":42}');
+
+shouldBe(JSON.stringify(object, [ Symbol('Cocoa') ]), "{}");
+
+// The property that value is Symbol will be ignored.
+shouldBe(JSON.stringify({ cocoa: Symbol('Cocoa'), cappuccino: Symbol('Cappuccino') }), '{}');
+shouldBe(JSON.stringify({ cocoa: Symbol('Cocoa'), cappuccino: 'cappuccino', [Symbol('Matcha')]: 'matcha' }), '{"cappuccino":"cappuccino"}');
+var object = {foo: Symbol()};
+object[Symbol()] = 1;
+shouldBe(JSON.stringify(object), '{}');
 
-if (JSON.stringify(object, [ Symbol('Cocoa') ]) !== "{}")
-    throw "Error: bad value " + JSON.stringify(object, [ Symbol('Cocoa') ]);
+// The symbol value included in Array will be converted to null
+shouldBe(JSON.stringify([ Symbol('Cocoa') ]), '[null]');
+shouldBe(JSON.stringify([ "hello", Symbol('Cocoa'), 'world' ]), '["hello",null,"world"]');
+var array = [Symbol()];
+shouldBe(JSON.stringify(array), '[null]');