diff options
author | Ivan Tkachenko <me@ratijas.tk> | 2023-02-20 03:30:37 +0300 |
---|---|---|
committer | Ivan Tkachenko <me@ratijas.tk> | 2023-02-22 18:21:09 +0300 |
commit | 4c930247e67ecad383aa7e8d9a3308b38629bef8 (patch) | |
tree | 4aca9540a12fbd01f7a57db1232bef562134d8e2 /tests/auto/qml/qjsengine | |
parent | c769e1a8db9a5762bb80cbc34473ec48a670a038 (diff) |
Set correct `this` value in JSON.stringify replacer scope
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 <ulf.hermann@qt.io>
Diffstat (limited to 'tests/auto/qml/qjsengine')
-rw-r--r-- | tests/auto/qml/qjsengine/tst_qjsengine.cpp | 59 |
1 files changed, 57 insertions, 2 deletions
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<QString>("object"); + QTest::addColumn<QString>("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 |