From 4c930247e67ecad383aa7e8d9a3308b38629bef8 Mon Sep 17 00:00:00 2001 From: Ivan Tkachenko Date: Mon, 20 Feb 2023 03:30:37 +0300 Subject: Set correct `this` value in JSON.stringify replacer scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Long story short, JSON.stringify takes optional second argument which can be a replacer function. It is supposed to be called for each encountered key-value during serialization. While replacer's second argument is already serialized (so you could just return it), just in case you are not satisfied with default serialization — the original value is still passed in together with the whole object as a value of JavaScript `this`. Sadly, there is no test in the whole ECMA test suite to catch this bug. This is quite a breaking change for code which already uses Qt-specific workarounds, so it would be better not to cherry-pick it as a hot-fix onto existing (released) branches. Sample use case: serialize dates as a number of seconds since epoch. function replacer(k, v) { if (this[k] instanceof Date) { return Math.floor(this[k].getTime() / 1000.0); } return v; } const str = JSON.stringify(obj, replacer); [ChangeLog][QML][Important Behavior Changes] Set correct `this` value in JSON.stringify replacer scope, so that the value for current key in current object is no longer pre-stringified, and can actually be used to implement custom object serialization logic. Fixes: QTBUG-95324 Pick-to: 6.5 Change-Id: I618a533e8eba7999d5416aca14f4971093a83f7a Reviewed-by: Ulf Hermann --- tests/auto/qml/qjsengine/tst_qjsengine.cpp | 59 +++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) (limited to 'tests/auto/qml/qjsengine') diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 21f4ff033b..3106a7a383 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -136,7 +136,10 @@ private slots: void reentrancy_Array(); void reentrancy_objectCreation(); void jsIncDecNonObjectProperty(); - void JSONparse(); + void JSON_Parse(); + void JSON_Stringify_data(); + void JSON_Stringify(); + void JSON_Stringify_WithReplacer_QTBUG_95324(); void arraySort(); void lookupOnDisappearingProperty(); void arrayConcat(); @@ -3305,13 +3308,65 @@ void tst_QJSEngine::jsIncDecNonObjectProperty() } } -void tst_QJSEngine::JSONparse() +void tst_QJSEngine::JSON_Parse() { QJSEngine eng; QJSValue ret = eng.evaluate("var json=\"{\\\"1\\\": null}\"; JSON.parse(json);"); QVERIFY(ret.isObject()); } +void tst_QJSEngine::JSON_Stringify_data() +{ + QTest::addColumn("object"); + QTest::addColumn("json"); + + // Basic "smoke" test. More tests are provided by test262 suite. + // Don't test with multiple key-value pairs on the same level, + // because serialization order might not be deterministic. + // Note: parenthesis are required, otherwise objects will be interpretted as code blocks. + QTest::newRow("empty") << "({})" << "{}"; + QTest::newRow("string") << "({a: 'b'})" << "{\"a\":\"b\"}"; + QTest::newRow("number") << "({c: 42})" << "{\"c\":42}"; + QTest::newRow("boolean") << "({d: true})" << "{\"d\":true}"; + QTest::newRow("key is array") << "({[[12, 34]]: 56})" << "{\"12,34\":56}"; + QTest::newRow("value is date") << "({d: new Date('2000-01-20T12:00:00.000Z')})" << "{\"d\":\"2000-01-20T12:00:00.000Z\"}"; +} + +void tst_QJSEngine::JSON_Stringify() +{ + QFETCH(QString, object); + QFETCH(QString, json); + + QJSEngine eng; + + QJSValue obj = eng.evaluate(object); + QVERIFY(obj.isObject()); + + QJSValue func = eng.evaluate("(function(obj) { return JSON.stringify(obj); })"); + QVERIFY(func.isCallable()); + + QJSValue ret = func.call(QJSValueList{obj}); + QVERIFY(ret.isString()); + QCOMPARE(ret.toString(), json); +} + +void tst_QJSEngine::JSON_Stringify_WithReplacer_QTBUG_95324() +{ + QJSEngine eng; + QJSValue json = eng.evaluate(R"( + function replacer(k, v) { + if (this[k] instanceof Date) { + return Math.floor(this[k].getTime() / 1000.0); + } + return v; + } + const obj = {d: new Date('2000-01-20T12:00:00.000Z')}; + JSON.stringify(obj, replacer); + )"); + QVERIFY(json.isString()); + QCOMPARE(json.toString(), QString::fromLatin1("{\"d\":948369600}")); +} + void tst_QJSEngine::arraySort() { // tests that calling Array.sort with a bad sort function doesn't cause issues -- cgit v1.2.3