diff options
Diffstat (limited to 'tests/auto/corelib/serialization/json/tst_qtjson.cpp')
-rw-r--r-- | tests/auto/corelib/serialization/json/tst_qtjson.cpp | 2990 |
1 files changed, 2990 insertions, 0 deletions
diff --git a/tests/auto/corelib/serialization/json/tst_qtjson.cpp b/tests/auto/corelib/serialization/json/tst_qtjson.cpp new file mode 100644 index 0000000000..1e3604ac9e --- /dev/null +++ b/tests/auto/corelib/serialization/json/tst_qtjson.cpp @@ -0,0 +1,2990 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest> + +#include "qjsonarray.h" +#include "qjsonobject.h" +#include "qjsonvalue.h" +#include "qjsondocument.h" +#include "qregularexpression.h" +#include <limits> + +#define INVALID_UNICODE "\xCE\xBA\xE1" +#define UNICODE_NON_CHARACTER "\xEF\xBF\xBF" +#define UNICODE_DJE "\320\202" // Character from the Serbian Cyrillic alphabet + +class tst_QtJson: public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + + void testValueSimple(); + void testNumbers(); + void testNumbers_2(); + void testNumbers_3(); + void testNumbers_4(); + + void testObjectSimple(); + void testObjectSmallKeys(); + void testArraySimple(); + void testValueObject(); + void testValueArray(); + void testObjectNested(); + void testArrayNested(); + void testArrayNestedEmpty(); + void testArrayComfortOperators(); + void testObjectNestedEmpty(); + + void testValueRef(); + void testObjectIteration(); + void testArrayIteration(); + + void testObjectFind(); + + void testDocument(); + + void nullValues(); + void nullArrays(); + void nullObject(); + void constNullObject(); + + void keySorting(); + + void undefinedValues(); + + void fromVariant_data(); + void fromVariant(); + void toVariant_data(); + void toVariant(); + void fromVariantMap(); + void fromVariantHash(); + void toVariantMap(); + void toVariantHash(); + void toVariantList(); + + void toJson(); + void toJsonSillyNumericValues(); + void toJsonLargeNumericValues(); + void fromJson(); + void fromJsonErrors(); + void fromBinary(); + void toAndFromBinary_data(); + void toAndFromBinary(); + void invalidBinaryData(); + void parseNumbers(); + void parseStrings(); + void parseDuplicateKeys(); + void testParser(); + + void compactArray(); + void compactObject(); + + void validation(); + + void assignToDocument(); + + void testDuplicateKeys(); + void testCompaction(); + void testDebugStream(); + void testCompactionError(); + + void parseUnicodeEscapes(); + + void assignObjects(); + void assignArrays(); + + void testTrailingComma(); + void testDetachBug(); + void testJsonValueRefDefault(); + + void valueEquals(); + void objectEquals_data(); + void objectEquals(); + void arrayEquals_data(); + void arrayEquals(); + + void bom(); + void nesting(); + + void longStrings(); + + void arrayInitializerList(); + void objectInitializerList(); + + void unicodeKeys(); + void garbageAtEnd(); + + void removeNonLatinKey(); + void documentFromVariant(); + + void parseErrorOffset_data(); + void parseErrorOffset(); + + void implicitValueType(); + void implicitDocumentType(); + +private: + QString testDataDir; +}; + +void tst_QtJson::initTestCase() +{ + testDataDir = QFileInfo(QFINDTESTDATA("test.json")).absolutePath(); + if (testDataDir.isEmpty()) + testDataDir = QCoreApplication::applicationDirPath(); +} + +void tst_QtJson::testValueSimple() +{ + QJsonObject object; + object.insert("number", 999.); + QJsonArray array; + for (int i = 0; i < 10; ++i) + array.append((double)i); + + QJsonValue value(true); + QCOMPARE(value.type(), QJsonValue::Bool); + QCOMPARE(value.toDouble(), 0.); + QCOMPARE(value.toString(), QString()); + QCOMPARE(value.toBool(), true); + QCOMPARE(value.toObject(), QJsonObject()); + QCOMPARE(value.toArray(), QJsonArray()); + QCOMPARE(value.toDouble(99.), 99.); + QCOMPARE(value.toString(QString("test")), QString("test")); + QCOMPARE(value.toObject(object), object); + QCOMPARE(value.toArray(array), array); + + value = 999.; + QCOMPARE(value.type(), QJsonValue::Double); + QCOMPARE(value.toDouble(), 999.); + QCOMPARE(value.toString(), QString()); + QCOMPARE(value.toBool(), false); + QCOMPARE(value.toBool(true), true); + QCOMPARE(value.toObject(), QJsonObject()); + QCOMPARE(value.toArray(), QJsonArray()); + + value = QLatin1String("test"); + QCOMPARE(value.toDouble(), 0.); + QCOMPARE(value.toString(), QLatin1String("test")); + QCOMPARE(value.toBool(), false); + QCOMPARE(value.toObject(), QJsonObject()); + QCOMPARE(value.toArray(), QJsonArray()); +} + +void tst_QtJson::testNumbers() +{ + { + int numbers[] = { + 0, + -1, + 1, + (1<<26), + (1<<27), + (1<<28), + -(1<<26), + -(1<<27), + -(1<<28), + (1<<26) - 1, + (1<<27) - 1, + (1<<28) - 1, + -((1<<26) - 1), + -((1<<27) - 1), + -((1<<28) - 1) + }; + int n = sizeof(numbers)/sizeof(int); + + QJsonArray array; + for (int i = 0; i < n; ++i) + array.append((double)numbers[i]); + + QByteArray serialized = QJsonDocument(array).toJson(); + QJsonDocument json = QJsonDocument::fromJson(serialized); + QJsonArray array2 = json.array(); + + QCOMPARE(array.size(), array2.size()); + for (int i = 0; i < array.size(); ++i) { + QCOMPARE(array.at(i).type(), QJsonValue::Double); + QCOMPARE(array.at(i).toDouble(), (double)numbers[i]); + QCOMPARE(array2.at(i).type(), QJsonValue::Double); + QCOMPARE(array2.at(i).toDouble(), (double)numbers[i]); + } + } + + { + qint64 numbers[] = { + 0, + -1, + 1, + (1ll<<54), + (1ll<<55), + (1ll<<56), + -(1ll<<54), + -(1ll<<55), + -(1ll<<56), + (1ll<<54) - 1, + (1ll<<55) - 1, + (1ll<<56) - 1, + -((1ll<<54) - 1), + -((1ll<<55) - 1), + -((1ll<<56) - 1) + }; + int n = sizeof(numbers)/sizeof(qint64); + + QJsonArray array; + for (int i = 0; i < n; ++i) + array.append((double)numbers[i]); + + QByteArray serialized = QJsonDocument(array).toJson(); + QJsonDocument json = QJsonDocument::fromJson(serialized); + QJsonArray array2 = json.array(); + + QCOMPARE(array.size(), array2.size()); + for (int i = 0; i < array.size(); ++i) { + QCOMPARE(array.at(i).type(), QJsonValue::Double); + QCOMPARE(array.at(i).toDouble(), (double)numbers[i]); + QCOMPARE(array2.at(i).type(), QJsonValue::Double); + QCOMPARE(array2.at(i).toDouble(), (double)numbers[i]); + } + } + + { + double numbers[] = { + 0, + -1, + 1, + double(1ll<<54), + double(1ll<<55), + double(1ll<<56), + double(-(1ll<<54)), + double(-(1ll<<55)), + double(-(1ll<<56)), + double((1ll<<54) - 1), + double((1ll<<55) - 1), + double((1ll<<56) - 1), + double(-((1ll<<54) - 1)), + double(-((1ll<<55) - 1)), + double(-((1ll<<56) - 1)), + 1.1, + 0.1, + -0.1, + -1.1, + 1e200, + -1e200 + }; + int n = sizeof(numbers)/sizeof(double); + + QJsonArray array; + for (int i = 0; i < n; ++i) + array.append(numbers[i]); + + QByteArray serialized = QJsonDocument(array).toJson(); + QJsonDocument json = QJsonDocument::fromJson(serialized); + QJsonArray array2 = json.array(); + + QCOMPARE(array.size(), array2.size()); + for (int i = 0; i < array.size(); ++i) { + QCOMPARE(array.at(i).type(), QJsonValue::Double); + QCOMPARE(array.at(i).toDouble(), numbers[i]); + QCOMPARE(array2.at(i).type(), QJsonValue::Double); + QCOMPARE(array2.at(i).toDouble(), numbers[i]); + } + } + +} + +void tst_QtJson::testNumbers_2() +{ + // test cases from TC39 test suite for ECMAScript + // http://hg.ecmascript.org/tests/test262/file/d067d2f0ca30/test/suite/ch08/8.5/8.5.1.js + + // Fill an array with 2 to the power of (0 ... -1075) + double value = 1; + double floatValues[1076], floatValues_1[1076]; + QJsonObject jObject; + for (int power = 0; power <= 1075; power++) { + floatValues[power] = value; + jObject.insert(QString::number(power), QJsonValue(floatValues[power])); + // Use basic math operations for testing, which are required to support 'gradual underflow' rather + // than Math.pow etc..., which are defined as 'implementation dependent'. + value = value * 0.5; + } + + QJsonDocument jDocument1(jObject); + QByteArray ba(jDocument1.toJson()); + + QJsonDocument jDocument2(QJsonDocument::fromJson(ba)); + for (int power = 0; power <= 1075; power++) { + floatValues_1[power] = jDocument2.object().value(QString::number(power)).toDouble(); +#ifdef Q_OS_QNX + if (power >= 970) + QEXPECT_FAIL("", "See QTBUG-37066", Abort); +#endif + QVERIFY2(floatValues[power] == floatValues_1[power], QString("floatValues[%1] != floatValues_1[%1]").arg(power).toLatin1()); + } + + // The last value is below min denorm and should round to 0, everything else should contain a value + QVERIFY2(floatValues_1[1075] == 0, "Value after min denorm should round to 0"); + + // Validate the last actual value is min denorm + QVERIFY2(floatValues_1[1074] == 4.9406564584124654417656879286822e-324, QString("Min denorm value is incorrect: %1").arg(floatValues_1[1074]).toLatin1()); + + // Validate that every value is half the value before it up to 1 + for (int index = 1074; index > 0; index--) { + QVERIFY2(floatValues_1[index] != 0, QString("2**- %1 should not be 0").arg(index).toLatin1()); + + QVERIFY2(floatValues_1[index - 1] == (floatValues_1[index] * 2), QString("Value should be double adjacent value at index %1").arg(index).toLatin1()); + } +} + +void tst_QtJson::testNumbers_3() +{ + // test case from QTBUG-31926 + double d1 = 1.123451234512345; + double d2 = 1.123451234512346; + + QJsonObject jObject; + jObject.insert("d1", QJsonValue(d1)); + jObject.insert("d2", QJsonValue(d2)); + QJsonDocument jDocument1(jObject); + QByteArray ba(jDocument1.toJson()); + + QJsonDocument jDocument2(QJsonDocument::fromJson(ba)); + + double d1_1(jDocument2.object().value("d1").toDouble()); + double d2_1(jDocument2.object().value("d2").toDouble()); + QVERIFY(d1_1 != d2_1); +} + +void tst_QtJson::testNumbers_4() +{ + // no exponent notation used to print numbers between -2^64 and 2^64 + QJsonArray array; + array << QJsonValue(+1000000000000000.0); + array << QJsonValue(-1000000000000000.0); + array << QJsonValue(+9007199254740992.0); + array << QJsonValue(-9007199254740992.0); + array << QJsonValue(+9223372036854775808.0); + array << QJsonValue(-9223372036854775808.0); + array << QJsonValue(+18446744073709551616.0); + array << QJsonValue(-18446744073709551616.0); + const QByteArray json(QJsonDocument(array).toJson()); + const QByteArray expected = + "[\n" + " 1000000000000000,\n" + " -1000000000000000,\n" + " 9007199254740992,\n" + " -9007199254740992,\n" + " 9223372036854776000,\n" + " -9223372036854776000,\n" + " 18446744073709552000,\n" + " -18446744073709552000\n" + "]\n"; + QCOMPARE(json, expected); +} + +void tst_QtJson::testObjectSimple() +{ + QJsonObject object; + object.insert("number", 999.); + QCOMPARE(object.value("number").type(), QJsonValue::Double); + QCOMPARE(object.value(QLatin1String("number")).toDouble(), 999.); + object.insert("string", QString::fromLatin1("test")); + QCOMPARE(object.value("string").type(), QJsonValue::String); + QCOMPARE(object.value(QLatin1String("string")).toString(), QString("test")); + object.insert("boolean", true); + QCOMPARE(object.value("boolean").toBool(), true); + QCOMPARE(object.value(QLatin1String("boolean")).toBool(), true); + + QStringList keys = object.keys(); + QVERIFY2(keys.contains("number"), "key number not found"); + QVERIFY2(keys.contains("string"), "key string not found"); + QVERIFY2(keys.contains("boolean"), "key boolean not found"); + + // if we put a JsonValue into the JsonObject and retrieve + // it, it should be identical. + QJsonValue value(QLatin1String("foo")); + object.insert("value", value); + QCOMPARE(object.value("value"), value); + + int size = object.size(); + object.remove("boolean"); + QCOMPARE(object.size(), size - 1); + QVERIFY2(!object.contains("boolean"), "key boolean should have been removed"); + + QJsonValue taken = object.take("value"); + QCOMPARE(taken, value); + QVERIFY2(!object.contains("value"), "key value should have been removed"); + + QString before = object.value("string").toString(); + object.insert("string", QString::fromLatin1("foo")); + QVERIFY2(object.value(QLatin1String("string")).toString() != before, "value should have been updated"); + + size = object.size(); + QJsonObject subobject; + subobject.insert("number", 42); + subobject.insert(QLatin1String("string"), QLatin1String("foobar")); + object.insert("subobject", subobject); + QCOMPARE(object.size(), size+1); + QJsonValue subvalue = object.take(QLatin1String("subobject")); + QCOMPARE(object.size(), size); + QCOMPARE(subvalue.toObject(), subobject); + // make object detach by modifying it many times + for (int i = 0; i < 64; ++i) + object.insert(QLatin1String("string"), QLatin1String("bar")); + QCOMPARE(object.size(), size); + QCOMPARE(subvalue.toObject(), subobject); +} + +void tst_QtJson::testObjectSmallKeys() +{ + QJsonObject data1; + data1.insert(QStringLiteral("1"), 123.); + QVERIFY(data1.contains(QStringLiteral("1"))); + QCOMPARE(data1.value(QStringLiteral("1")).toDouble(), (double)123); + data1.insert(QStringLiteral("12"), 133.); + QCOMPARE(data1.value(QStringLiteral("12")).toDouble(), (double)133); + QVERIFY(data1.contains(QStringLiteral("12"))); + data1.insert(QStringLiteral("123"), 323.); + QCOMPARE(data1.value(QStringLiteral("12")).toDouble(), (double)133); + QVERIFY(data1.contains(QStringLiteral("123"))); + QCOMPARE(data1.value(QStringLiteral("123")).type(), QJsonValue::Double); + QCOMPARE(data1.value(QStringLiteral("123")).toDouble(), (double)323); +} + +void tst_QtJson::testArraySimple() +{ + QJsonArray array; + array.append(999.); + array.append(QString::fromLatin1("test")); + array.append(true); + + QJsonValue val = array.at(0); + QCOMPARE(array.at(0).toDouble(), 999.); + QCOMPARE(array.at(1).toString(), QString("test")); + QCOMPARE(array.at(2).toBool(), true); + QCOMPARE(array.size(), 3); + + // if we put a JsonValue into the JsonArray and retrieve + // it, it should be identical. + QJsonValue value(QLatin1String("foo")); + array.append(value); + QCOMPARE(array.at(3), value); + + int size = array.size(); + array.removeAt(2); + --size; + QCOMPARE(array.size(), size); + + QJsonValue taken = array.takeAt(0); + --size; + QCOMPARE(taken.toDouble(), 999.); + QCOMPARE(array.size(), size); + + // check whether null values work + array.append(QJsonValue()); + ++size; + QCOMPARE(array.size(), size); + QCOMPARE(array.last().type(), QJsonValue::Null); + QCOMPARE(array.last(), QJsonValue()); + + QCOMPARE(array.first().type(), QJsonValue::String); + QCOMPARE(array.first(), QJsonValue(QLatin1String("test"))); + + array.prepend(false); + QCOMPARE(array.first().type(), QJsonValue::Bool); + QCOMPARE(array.first(), QJsonValue(false)); + + QCOMPARE(array.at(-1), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(array.at(array.size()), QJsonValue(QJsonValue::Undefined)); + + array.replace(0, -555.); + QCOMPARE(array.first().type(), QJsonValue::Double); + QCOMPARE(array.first(), QJsonValue(-555.)); + QCOMPARE(array.at(1).type(), QJsonValue::String); + QCOMPARE(array.at(1), QJsonValue(QLatin1String("test"))); +} + +void tst_QtJson::testValueObject() +{ + QJsonObject object; + object.insert("number", 999.); + object.insert("string", QLatin1String("test")); + object.insert("boolean", true); + + QJsonValue value(object); + + // if we don't modify the original JsonObject, toObject() + // on the JsonValue should return the same object (non-detached). + QCOMPARE(value.toObject(), object); + + // if we modify the original object, it should detach + object.insert("test", QJsonValue(QLatin1String("test"))); + QVERIFY2(value.toObject() != object, "object should have detached"); +} + +void tst_QtJson::testValueArray() +{ + QJsonArray array; + array.append(999.); + array.append(QLatin1String("test")); + array.append(true); + + QJsonValue value(array); + + // if we don't modify the original JsonArray, toArray() + // on the JsonValue should return the same object (non-detached). + QCOMPARE(value.toArray(), array); + + // if we modify the original array, it should detach + array.append(QLatin1String("test")); + QVERIFY2(value.toArray() != array, "array should have detached"); +} + +void tst_QtJson::testObjectNested() +{ + QJsonObject inner, outer; + inner.insert("number", 999.); + outer.insert("nested", inner); + + // if we don't modify the original JsonObject, value() + // should return the same object (non-detached). + QJsonObject value = outer.value("nested").toObject(); + QCOMPARE(value, inner); + QCOMPARE(value.value("number").toDouble(), 999.); + + // if we modify the original object, it should detach and not + // affect the nested object + inner.insert("number", 555.); + value = outer.value("nested").toObject(); + QVERIFY2(inner.value("number").toDouble() != value.value("number").toDouble(), + "object should have detached"); + + // array in object + QJsonArray array; + array.append(123.); + array.append(456.); + outer.insert("array", array); + QCOMPARE(outer.value("array").toArray(), array); + QCOMPARE(outer.value("array").toArray().at(1).toDouble(), 456.); + + // two deep objects + QJsonObject twoDeep; + twoDeep.insert("boolean", true); + inner.insert("nested", twoDeep); + outer.insert("nested", inner); + QCOMPARE(outer.value("nested").toObject().value("nested").toObject(), twoDeep); + QCOMPARE(outer.value("nested").toObject().value("nested").toObject().value("boolean").toBool(), + true); +} + +void tst_QtJson::testArrayNested() +{ + QJsonArray inner, outer; + inner.append(999.); + outer.append(inner); + + // if we don't modify the original JsonArray, value() + // should return the same array (non-detached). + QJsonArray value = outer.at(0).toArray(); + QCOMPARE(value, inner); + QCOMPARE(value.at(0).toDouble(), 999.); + + // if we modify the original array, it should detach and not + // affect the nested array + inner.append(555.); + value = outer.at(0).toArray(); + QVERIFY2(inner.size() != value.size(), "array should have detached"); + + // objects in arrays + QJsonObject object; + object.insert("boolean", true); + outer.append(object); + QCOMPARE(outer.last().toObject(), object); + QCOMPARE(outer.last().toObject().value("boolean").toBool(), true); + + // two deep arrays + QJsonArray twoDeep; + twoDeep.append(QJsonValue(QString::fromLatin1("nested"))); + inner.append(twoDeep); + outer.append(inner); + QCOMPARE(outer.last().toArray().last().toArray(), twoDeep); + QCOMPARE(outer.last().toArray().last().toArray().at(0).toString(), QString("nested")); +} + +void tst_QtJson::testArrayNestedEmpty() +{ + QJsonObject object; + QJsonArray inner; + object.insert("inner", inner); + QJsonValue val = object.value("inner"); + QJsonArray value = object.value("inner").toArray(); + QCOMPARE(value.size(), 0); + QCOMPARE(value, inner); + QCOMPARE(value.size(), 0); + object.insert("count", 0.); + QCOMPARE(object.value("inner").toArray().size(), 0); + QVERIFY(object.value("inner").toArray().isEmpty()); + QJsonDocument(object).toBinaryData(); + QCOMPARE(object.value("inner").toArray().size(), 0); +} + +void tst_QtJson::testObjectNestedEmpty() +{ + QJsonObject object; + QJsonObject inner; + QJsonObject inner2; + object.insert("inner", inner); + object.insert("inner2", inner2); + QJsonObject value = object.value("inner").toObject(); + QCOMPARE(value.size(), 0); + QCOMPARE(value, inner); + QCOMPARE(value.size(), 0); + object.insert("count", 0.); + QCOMPARE(object.value("inner").toObject().size(), 0); + QCOMPARE(object.value("inner").type(), QJsonValue::Object); + QJsonDocument(object).toBinaryData(); + QVERIFY(object.value("inner").toObject().isEmpty()); + QVERIFY(object.value("inner2").toObject().isEmpty()); + QJsonDocument doc = QJsonDocument::fromBinaryData(QJsonDocument(object).toBinaryData()); + QVERIFY(!doc.isNull()); + QJsonObject reconstituted(doc.object()); + QCOMPARE(reconstituted.value("inner").toObject().size(), 0); + QCOMPARE(reconstituted.value("inner").type(), QJsonValue::Object); + QCOMPARE(reconstituted.value("inner2").type(), QJsonValue::Object); +} + +void tst_QtJson::testArrayComfortOperators() +{ + QJsonArray first; + first.append(123.); + first.append(QLatin1String("foo")); + + QJsonArray second = QJsonArray() << 123. << QLatin1String("foo"); + QCOMPARE(first, second); + + first = first + QLatin1String("bar"); + second += QLatin1String("bar"); + QCOMPARE(first, second); +} + +void tst_QtJson::testValueRef() +{ + QJsonArray array; + array.append(1.); + array.append(2.); + array.append(3.); + array.append(4); + array.append(4.1); + array[1] = false; + + QCOMPARE(array.size(), 5); + QCOMPARE(array.at(0).toDouble(), 1.); + QCOMPARE(array.at(2).toDouble(), 3.); + QCOMPARE(array.at(3).toInt(), 4); + QCOMPARE(array.at(4).toInt(), 0); + QCOMPARE(array.at(1).type(), QJsonValue::Bool); + QCOMPARE(array.at(1).toBool(), false); + + QJsonObject object; + object[QLatin1String("key")] = true; + QCOMPARE(object.size(), 1); + object.insert(QLatin1String("null"), QJsonValue()); + QCOMPARE(object.value(QLatin1String("null")), QJsonValue()); + object[QLatin1String("null")] = 100.; + QCOMPARE(object.value(QLatin1String("null")).type(), QJsonValue::Double); + QJsonValue val = qAsConst(object)[QLatin1String("null")]; + QCOMPARE(val.toDouble(), 100.); + QCOMPARE(object.size(), 2); + + array[1] = array[2] = object[QLatin1String("key")] = 42; + QCOMPARE(array[1], array[2]); + QCOMPARE(array[2], object[QLatin1String("key")]); + QCOMPARE(object.value(QLatin1String("key")), QJsonValue(42)); +} + +void tst_QtJson::testObjectIteration() +{ + QJsonObject object; + + for (QJsonObject::iterator it = object.begin(); it != object.end(); ++it) + QVERIFY(false); + + const QString property = "kkk"; + object.insert(property, 11); + object.take(property); + for (QJsonObject::iterator it = object.begin(); it != object.end(); ++it) + QVERIFY(false); + + for (int i = 0; i < 10; ++i) + object[QString::number(i)] = (double)i; + + QCOMPARE(object.size(), 10); + + QCOMPARE(object.begin()->toDouble(), object.constBegin()->toDouble()); + + for (QJsonObject::iterator it = object.begin(); it != object.end(); ++it) { + QJsonValue value = it.value(); + QCOMPARE((double)it.key().toInt(), value.toDouble()); + } + + { + QJsonObject object2 = object; + QCOMPARE(object, object2); + + QJsonValue val = *object2.begin(); + object2.erase(object2.begin()); + QCOMPARE(object.size(), 10); + QCOMPARE(object2.size(), 9); + + for (QJsonObject::const_iterator it = object2.constBegin(); it != object2.constEnd(); ++it) { + QJsonValue value = it.value(); + QVERIFY(it.value() != val); + QCOMPARE((double)it.key().toInt(), value.toDouble()); + } + } + + { + QJsonObject object2 = object; + QCOMPARE(object, object2); + + QJsonObject::iterator it = object2.find(QString::number(5)); + object2.erase(it); + QCOMPARE(object.size(), 10); + QCOMPARE(object2.size(), 9); + } + + { + QJsonObject::Iterator it = object.begin(); + it += 5; + QCOMPARE(QJsonValue(it.value()).toDouble(), 5.); + it -= 3; + QCOMPARE(QJsonValue(it.value()).toDouble(), 2.); + QJsonObject::Iterator it2 = it + 5; + QCOMPARE(QJsonValue(it2.value()).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(QJsonValue(it2.value()).toDouble(), 1.); + } + + { + QJsonObject::ConstIterator it = object.constBegin(); + it += 5; + QCOMPARE(QJsonValue(it.value()).toDouble(), 5.); + it -= 3; + QCOMPARE(QJsonValue(it.value()).toDouble(), 2.); + QJsonObject::ConstIterator it2 = it + 5; + QCOMPARE(QJsonValue(it2.value()).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(QJsonValue(it2.value()).toDouble(), 1.); + } + + QJsonObject::Iterator it = object.begin(); + while (!object.isEmpty()) + it = object.erase(it); + QCOMPARE(object.size() , 0); + QCOMPARE(it, object.end()); +} + +void tst_QtJson::testArrayIteration() +{ + QJsonArray array; + for (int i = 0; i < 10; ++i) + array.append(i); + + QCOMPARE(array.size(), 10); + + int i = 0; + for (QJsonArray::iterator it = array.begin(); it != array.end(); ++it, ++i) { + QJsonValue value = (*it); + QCOMPARE((double)i, value.toDouble()); + } + + QCOMPARE(array.begin()->toDouble(), array.constBegin()->toDouble()); + + { + QJsonArray array2 = array; + QCOMPARE(array, array2); + + QJsonValue val = *array2.begin(); + array2.erase(array2.begin()); + QCOMPARE(array.size(), 10); + QCOMPARE(array2.size(), 9); + + i = 1; + for (QJsonArray::const_iterator it = array2.constBegin(); it != array2.constEnd(); ++it, ++i) { + QJsonValue value = (*it); + QCOMPARE((double)i, value.toDouble()); + } + } + + { + QJsonArray::Iterator it = array.begin(); + it += 5; + QCOMPARE(QJsonValue((*it)).toDouble(), 5.); + it -= 3; + QCOMPARE(QJsonValue((*it)).toDouble(), 2.); + QJsonArray::Iterator it2 = it + 5; + QCOMPARE(QJsonValue(*it2).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(QJsonValue(*it2).toDouble(), 1.); + } + + { + QJsonArray::ConstIterator it = array.constBegin(); + it += 5; + QCOMPARE(QJsonValue((*it)).toDouble(), 5.); + it -= 3; + QCOMPARE(QJsonValue((*it)).toDouble(), 2.); + QJsonArray::ConstIterator it2 = it + 5; + QCOMPARE(QJsonValue(*it2).toDouble(), 7.); + it2 = it - 1; + QCOMPARE(QJsonValue(*it2).toDouble(), 1.); + } + + QJsonArray::Iterator it = array.begin(); + while (!array.isEmpty()) + it = array.erase(it); + QCOMPARE(array.size() , 0); + QCOMPARE(it, array.end()); +} + +void tst_QtJson::testObjectFind() +{ + QJsonObject object; + for (int i = 0; i < 10; ++i) + object[QString::number(i)] = i; + + QCOMPARE(object.size(), 10); + + QJsonObject::iterator it = object.find(QLatin1String("1")); + QCOMPARE((*it).toDouble(), 1.); + it = object.find(QString("11")); + QCOMPARE((*it).type(), QJsonValue::Undefined); + QCOMPARE(it, object.end()); + + QJsonObject::const_iterator cit = object.constFind(QLatin1String("1")); + QCOMPARE((*cit).toDouble(), 1.); + cit = object.constFind(QString("11")); + QCOMPARE((*it).type(), QJsonValue::Undefined); + QCOMPARE(it, object.end()); +} + +void tst_QtJson::testDocument() +{ + QJsonDocument doc; + QCOMPARE(doc.isEmpty(), true); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), false); + + QJsonObject object; + doc.setObject(object); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + + object.insert(QLatin1String("Key"), QLatin1String("Value")); + doc.setObject(object); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QCOMPARE(doc.object(), object); + QCOMPARE(doc.array(), QJsonArray()); + + doc = QJsonDocument(); + QCOMPARE(doc.isEmpty(), true); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), false); + + QJsonArray array; + doc.setArray(array); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + + array.append(QLatin1String("Value")); + doc.setArray(array); + QCOMPARE(doc.isEmpty(), false); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QCOMPARE(doc.array(), array); + QCOMPARE(doc.object(), QJsonObject()); + + QJsonObject outer; + outer.insert(QLatin1String("outerKey"), 22); + QJsonObject inner; + inner.insert(QLatin1String("innerKey"), 42); + outer.insert(QLatin1String("innter"), inner); + QJsonArray innerArray; + innerArray.append(23); + outer.insert(QLatin1String("innterArray"), innerArray); + + QJsonDocument doc2(outer.value(QLatin1String("innter")).toObject()); + QVERIFY(doc2.object().contains(QLatin1String("innerKey"))); + QCOMPARE(doc2.object().value(QLatin1String("innerKey")), QJsonValue(42)); + + QJsonDocument doc3; + doc3.setObject(outer.value(QLatin1String("innter")).toObject()); + QCOMPARE(doc3.isArray(), false); + QCOMPARE(doc3.isObject(), true); + QVERIFY(doc3.object().contains(QString("innerKey"))); + QCOMPARE(doc3.object().value(QLatin1String("innerKey")), QJsonValue(42)); + + QJsonDocument doc4(outer.value(QLatin1String("innterArray")).toArray()); + QCOMPARE(doc4.isArray(), true); + QCOMPARE(doc4.isObject(), false); + QCOMPARE(doc4.array().size(), 1); + QCOMPARE(doc4.array().at(0), QJsonValue(23)); + + QJsonDocument doc5; + doc5.setArray(outer.value(QLatin1String("innterArray")).toArray()); + QCOMPARE(doc5.isArray(), true); + QCOMPARE(doc5.isObject(), false); + QCOMPARE(doc5.array().size(), 1); + QCOMPARE(doc5.array().at(0), QJsonValue(23)); +} + +void tst_QtJson::nullValues() +{ + QJsonArray array; + array.append(QJsonValue()); + + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0), QJsonValue()); + + QJsonObject object; + object.insert(QString("key"), QJsonValue()); + QCOMPARE(object.contains(QLatin1String("key")), true); + QCOMPARE(object.size(), 1); + QCOMPARE(object.value(QString("key")), QJsonValue()); +} + +void tst_QtJson::nullArrays() +{ + QJsonArray nullArray; + QJsonArray nonNull; + nonNull.append(QLatin1String("bar")); + + QCOMPARE(nullArray, QJsonArray()); + QVERIFY(nullArray != nonNull); + QVERIFY(nonNull != nullArray); + + QCOMPARE(nullArray.size(), 0); + QCOMPARE(nullArray.takeAt(0), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(nullArray.first(), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(nullArray.last(), QJsonValue(QJsonValue::Undefined)); + nullArray.removeAt(0); + nullArray.removeAt(-1); + + nullArray.append(QString("bar")); + nullArray.removeAt(0); + + QCOMPARE(nullArray.size(), 0); + QCOMPARE(nullArray.takeAt(0), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(nullArray.first(), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(nullArray.last(), QJsonValue(QJsonValue::Undefined)); + nullArray.removeAt(0); + nullArray.removeAt(-1); +} + +void tst_QtJson::nullObject() +{ + QJsonObject nullObject; + QJsonObject nonNull; + nonNull.insert(QLatin1String("foo"), QLatin1String("bar")); + + QCOMPARE(nullObject, QJsonObject()); + QVERIFY(nullObject != nonNull); + QVERIFY(nonNull != nullObject); + + QCOMPARE(nullObject.size(), 0); + QCOMPARE(nullObject.keys(), QStringList()); + nullObject.remove("foo"); + QCOMPARE(nullObject, QJsonObject()); + QCOMPARE(nullObject.take("foo"), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(nullObject.contains("foo"), false); + + nullObject.insert("foo", QString("bar")); + nullObject.remove("foo"); + + QCOMPARE(nullObject.size(), 0); + QCOMPARE(nullObject.keys(), QStringList()); + nullObject.remove("foo"); + QCOMPARE(nullObject, QJsonObject()); + QCOMPARE(nullObject.take("foo"), QJsonValue(QJsonValue::Undefined)); + QCOMPARE(nullObject.contains("foo"), false); +} + +void tst_QtJson::constNullObject() +{ + const QJsonObject nullObject; + QJsonObject nonNull; + nonNull.insert(QLatin1String("foo"), QLatin1String("bar")); + + QCOMPARE(nullObject, QJsonObject()); + QVERIFY(nullObject != nonNull); + QVERIFY(nonNull != nullObject); + + QCOMPARE(nullObject.size(), 0); + QCOMPARE(nullObject.keys(), QStringList()); + QCOMPARE(nullObject, QJsonObject()); + QCOMPARE(nullObject.contains("foo"), false); + QCOMPARE(nullObject["foo"], QJsonValue(QJsonValue::Undefined)); +} + +void tst_QtJson::keySorting() +{ + const char *json = "{ \"B\": true, \"A\": false }"; + QJsonDocument doc = QJsonDocument::fromJson(json); + + QCOMPARE(doc.isObject(), true); + + QJsonObject o = doc.object(); + QCOMPARE(o.size(), 2); + QJsonObject::const_iterator it = o.constBegin(); + QCOMPARE(it.key(), QLatin1String("A")); + ++it; + QCOMPARE(it.key(), QLatin1String("B")); + + QCOMPARE(o.keys(), QStringList() << QLatin1String("A") << QLatin1String("B")); +} + +void tst_QtJson::undefinedValues() +{ + QJsonObject object; + object.insert("Key", QJsonValue(QJsonValue::Undefined)); + QCOMPARE(object.size(), 0); + + object.insert("Key", QLatin1String("Value")); + QCOMPARE(object.size(), 1); + QCOMPARE(object.value("Key").type(), QJsonValue::String); + QCOMPARE(object.value("foo").type(), QJsonValue::Undefined); + object.insert("Key", QJsonValue(QJsonValue::Undefined)); + QCOMPARE(object.size(), 0); + QCOMPARE(object.value("Key").type(), QJsonValue::Undefined); + + QJsonArray array; + array.append(QJsonValue(QJsonValue::Undefined)); + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0).type(), QJsonValue::Null); + + QCOMPARE(array.at(1).type(), QJsonValue::Undefined); + QCOMPARE(array.at(-1).type(), QJsonValue::Undefined); +} + +void tst_QtJson::fromVariant_data() +{ + QTest::addColumn<QVariant>("variant"); + QTest::addColumn<QJsonValue>("jsonvalue"); + + bool boolValue = true; + int intValue = -1; + uint uintValue = 1; + long long longlongValue = -2; + unsigned long long ulonglongValue = 2; + float floatValue = 3.3f; + double doubleValue = 4.4; + QString stringValue("str"); + + QStringList stringList; + stringList.append(stringValue); + stringList.append("str2"); + QJsonArray jsonArray_string; + jsonArray_string.append(stringValue); + jsonArray_string.append("str2"); + + QVariantList variantList; + variantList.append(boolValue); + variantList.append(floatValue); + variantList.append(doubleValue); + variantList.append(stringValue); + variantList.append(stringList); + variantList.append(QVariant::fromValue(nullptr)); + QJsonArray jsonArray_variant; + jsonArray_variant.append(boolValue); + jsonArray_variant.append(floatValue); + jsonArray_variant.append(doubleValue); + jsonArray_variant.append(stringValue); + jsonArray_variant.append(jsonArray_string); + jsonArray_variant.append(QJsonValue(QJsonValue::Null)); + + QVariantMap variantMap; + variantMap["bool"] = boolValue; + variantMap["float"] = floatValue; + variantMap["string"] = stringValue; + variantMap["array"] = variantList; + QVariantHash variantHash; + variantHash["bool"] = boolValue; + variantHash["float"] = floatValue; + variantHash["string"] = stringValue; + variantHash["array"] = variantList; + QJsonObject jsonObject; + jsonObject["bool"] = boolValue; + jsonObject["float"] = floatValue; + jsonObject["string"] = stringValue; + jsonObject["array"] = jsonArray_variant; + + QTest::newRow("nullptr") << QVariant::fromValue(nullptr) << QJsonValue(QJsonValue::Null); + QTest::newRow("bool") << QVariant(boolValue) << QJsonValue(boolValue); + QTest::newRow("int") << QVariant(intValue) << QJsonValue(intValue); + QTest::newRow("uint") << QVariant(uintValue) << QJsonValue(static_cast<double>(uintValue)); + QTest::newRow("longlong") << QVariant(longlongValue) << QJsonValue(longlongValue); + QTest::newRow("ulonglong") << QVariant(ulonglongValue) << QJsonValue(static_cast<double>(ulonglongValue)); + QTest::newRow("float") << QVariant(floatValue) << QJsonValue(floatValue); + QTest::newRow("double") << QVariant(doubleValue) << QJsonValue(doubleValue); + QTest::newRow("string") << QVariant(stringValue) << QJsonValue(stringValue); + QTest::newRow("stringList") << QVariant(stringList) << QJsonValue(jsonArray_string); + QTest::newRow("variantList") << QVariant(variantList) << QJsonValue(jsonArray_variant); + QTest::newRow("variantMap") << QVariant(variantMap) << QJsonValue(jsonObject); + QTest::newRow("variantHash") << QVariant(variantHash) << QJsonValue(jsonObject); +} + +void tst_QtJson::fromVariant() +{ + QFETCH( QVariant, variant ); + QFETCH( QJsonValue, jsonvalue ); + + QCOMPARE(QJsonValue::fromVariant(variant), jsonvalue); + QCOMPARE(variant.toJsonValue(), jsonvalue); +} + +void tst_QtJson::toVariant_data() +{ + fromVariant_data(); +} + +void tst_QtJson::toVariant() +{ + QFETCH( QVariant, variant ); + QFETCH( QJsonValue, jsonvalue ); + + QCOMPARE(jsonvalue.toVariant(), variant); +} + +void tst_QtJson::fromVariantMap() +{ + QVariantMap map; + map.insert(QLatin1String("key1"), QLatin1String("value1")); + map.insert(QLatin1String("key2"), QLatin1String("value2")); + QJsonObject object = QJsonObject::fromVariantMap(map); + QCOMPARE(object.size(), 2); + QCOMPARE(object.value(QLatin1String("key1")), QJsonValue(QLatin1String("value1"))); + QCOMPARE(object.value(QLatin1String("key2")), QJsonValue(QLatin1String("value2"))); + + QVariantList list; + list.append(true); + list.append(QVariant()); + list.append(999.); + list.append(QLatin1String("foo")); + map.insert("list", list); + object = QJsonObject::fromVariantMap(map); + QCOMPARE(object.size(), 3); + QCOMPARE(object.value(QLatin1String("key1")), QJsonValue(QLatin1String("value1"))); + QCOMPARE(object.value(QLatin1String("key2")), QJsonValue(QLatin1String("value2"))); + QCOMPARE(object.value(QLatin1String("list")).type(), QJsonValue::Array); + QJsonArray array = object.value(QLatin1String("list")).toArray(); + QCOMPARE(array.size(), 4); + QCOMPARE(array.at(0).type(), QJsonValue::Bool); + QCOMPARE(array.at(0).toBool(), true); + QCOMPARE(array.at(1).type(), QJsonValue::Null); + QCOMPARE(array.at(2).type(), QJsonValue::Double); + QCOMPARE(array.at(2).toDouble(), 999.); + QCOMPARE(array.at(3).type(), QJsonValue::String); + QCOMPARE(array.at(3).toString(), QLatin1String("foo")); +} + +void tst_QtJson::fromVariantHash() +{ + QVariantHash map; + map.insert(QLatin1String("key1"), QLatin1String("value1")); + map.insert(QLatin1String("key2"), QLatin1String("value2")); + QJsonObject object = QJsonObject::fromVariantHash(map); + QCOMPARE(object.size(), 2); + QCOMPARE(object.value(QLatin1String("key1")), QJsonValue(QLatin1String("value1"))); + QCOMPARE(object.value(QLatin1String("key2")), QJsonValue(QLatin1String("value2"))); +} + +void tst_QtJson::toVariantMap() +{ + QCOMPARE(QMetaType::Type(QJsonValue(QJsonObject()).toVariant().type()), QMetaType::QVariantMap); // QTBUG-32524 + + QJsonObject object; + QVariantMap map = object.toVariantMap(); + QVERIFY(map.isEmpty()); + + object.insert("Key", QString("Value")); + object.insert("null", QJsonValue()); + QJsonArray array; + array.append(true); + array.append(999.); + array.append(QLatin1String("string")); + array.append(QJsonValue::Null); + object.insert("Array", array); + + map = object.toVariantMap(); + + QCOMPARE(map.size(), 3); + QCOMPARE(map.value("Key"), QVariant(QString("Value"))); + QCOMPARE(map.value("null"), QVariant::fromValue(nullptr)); + QCOMPARE(map.value("Array").type(), QVariant::List); + QVariantList list = map.value("Array").toList(); + QCOMPARE(list.size(), 4); + QCOMPARE(list.at(0), QVariant(true)); + QCOMPARE(list.at(1), QVariant(999.)); + QCOMPARE(list.at(2), QVariant(QLatin1String("string"))); + QCOMPARE(list.at(3), QVariant::fromValue(nullptr)); +} + +void tst_QtJson::toVariantHash() +{ + QJsonObject object; + QVariantHash hash = object.toVariantHash(); + QVERIFY(hash.isEmpty()); + + object.insert("Key", QString("Value")); + object.insert("null", QJsonValue::Null); + QJsonArray array; + array.append(true); + array.append(999.); + array.append(QLatin1String("string")); + array.append(QJsonValue::Null); + object.insert("Array", array); + + hash = object.toVariantHash(); + + QCOMPARE(hash.size(), 3); + QCOMPARE(hash.value("Key"), QVariant(QString("Value"))); + QCOMPARE(hash.value("null"), QVariant::fromValue(nullptr)); + QCOMPARE(hash.value("Array").type(), QVariant::List); + QVariantList list = hash.value("Array").toList(); + QCOMPARE(list.size(), 4); + QCOMPARE(list.at(0), QVariant(true)); + QCOMPARE(list.at(1), QVariant(999.)); + QCOMPARE(list.at(2), QVariant(QLatin1String("string"))); + QCOMPARE(list.at(3), QVariant::fromValue(nullptr)); +} + +void tst_QtJson::toVariantList() +{ + QCOMPARE(QMetaType::Type(QJsonValue(QJsonArray()).toVariant().type()), QMetaType::QVariantList); // QTBUG-32524 + + QJsonArray array; + QVariantList list = array.toVariantList(); + QVERIFY(list.isEmpty()); + + array.append(QString("Value")); + array.append(QJsonValue()); + QJsonArray inner; + inner.append(true); + inner.append(999.); + inner.append(QLatin1String("string")); + inner.append(QJsonValue()); + array.append(inner); + + list = array.toVariantList(); + + QCOMPARE(list.size(), 3); + QCOMPARE(list[0], QVariant(QString("Value"))); + QCOMPARE(list[1], QVariant::fromValue(nullptr)); + QCOMPARE(list[2].type(), QVariant::List); + QVariantList vlist = list[2].toList(); + QCOMPARE(vlist.size(), 4); + QCOMPARE(vlist.at(0), QVariant(true)); + QCOMPARE(vlist.at(1), QVariant(999.)); + QCOMPARE(vlist.at(2), QVariant(QLatin1String("string"))); + QCOMPARE(vlist.at(3), QVariant::fromValue(nullptr)); +} + +void tst_QtJson::toJson() +{ + // Test QJsonDocument::Indented format + { + QJsonObject object; + object.insert("\\Key\n", QString("Value")); + object.insert("null", QJsonValue()); + QJsonArray array; + array.append(true); + array.append(999.); + array.append(QLatin1String("string")); + array.append(QJsonValue()); + array.append(QLatin1String("\\\a\n\r\b\tabcABC\"")); + object.insert("Array", array); + + QByteArray json = QJsonDocument(object).toJson(); + + QByteArray expected = + "{\n" + " \"Array\": [\n" + " true,\n" + " 999,\n" + " \"string\",\n" + " null,\n" + " \"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"\n" + " ],\n" + " \"\\\\Key\\n\": \"Value\",\n" + " \"null\": null\n" + "}\n"; + QCOMPARE(json, expected); + + QJsonDocument doc; + doc.setObject(object); + json = doc.toJson(); + QCOMPARE(json, expected); + + doc.setArray(array); + json = doc.toJson(); + expected = + "[\n" + " true,\n" + " 999,\n" + " \"string\",\n" + " null,\n" + " \"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"\n" + "]\n"; + QCOMPARE(json, expected); + } + + // Test QJsonDocument::Compact format + { + QJsonObject object; + object.insert("\\Key\n", QString("Value")); + object.insert("null", QJsonValue()); + QJsonArray array; + array.append(true); + array.append(999.); + array.append(QLatin1String("string")); + array.append(QJsonValue()); + array.append(QLatin1String("\\\a\n\r\b\tabcABC\"")); + object.insert("Array", array); + + QByteArray json = QJsonDocument(object).toJson(QJsonDocument::Compact); + QByteArray expected = + "{\"Array\":[true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"],\"\\\\Key\\n\":\"Value\",\"null\":null}"; + QCOMPARE(json, expected); + + QJsonDocument doc; + doc.setObject(object); + json = doc.toJson(QJsonDocument::Compact); + QCOMPARE(json, expected); + + doc.setArray(array); + json = doc.toJson(QJsonDocument::Compact); + expected = "[true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"]"; + QCOMPARE(json, expected); + } +} + +void tst_QtJson::toJsonSillyNumericValues() +{ + QJsonObject object; + QJsonArray array; + array.append(QJsonValue(std::numeric_limits<double>::infinity())); // encode to: null + array.append(QJsonValue(-std::numeric_limits<double>::infinity())); // encode to: null + array.append(QJsonValue(std::numeric_limits<double>::quiet_NaN())); // encode to: null + object.insert("Array", array); + + QByteArray json = QJsonDocument(object).toJson(); + + QByteArray expected = + "{\n" + " \"Array\": [\n" + " null,\n" + " null,\n" + " null\n" + " ]\n" + "}\n"; + + QCOMPARE(json, expected); + + QJsonDocument doc; + doc.setObject(object); + json = doc.toJson(); + QCOMPARE(json, expected); +} + +void tst_QtJson::toJsonLargeNumericValues() +{ + QJsonObject object; + QJsonArray array; + array.append(QJsonValue(1.234567)); // actual precision bug in Qt 5.0.0 + array.append(QJsonValue(1.7976931348623157e+308)); // JS Number.MAX_VALUE + array.append(QJsonValue(5e-324)); // JS Number.MIN_VALUE + array.append(QJsonValue(std::numeric_limits<double>::min())); + array.append(QJsonValue(std::numeric_limits<double>::max())); + array.append(QJsonValue(std::numeric_limits<double>::epsilon())); + array.append(QJsonValue(std::numeric_limits<double>::denorm_min())); + array.append(QJsonValue(0.0)); + array.append(QJsonValue(-std::numeric_limits<double>::min())); + array.append(QJsonValue(-std::numeric_limits<double>::max())); + array.append(QJsonValue(-std::numeric_limits<double>::epsilon())); + array.append(QJsonValue(-std::numeric_limits<double>::denorm_min())); + array.append(QJsonValue(-0.0)); + array.append(QJsonValue(9007199254740992LL)); // JS Number max integer + array.append(QJsonValue(-9007199254740992LL)); // JS Number min integer + object.insert("Array", array); + + QByteArray json = QJsonDocument(object).toJson(); + + QByteArray expected = + "{\n" + " \"Array\": [\n" + " 1.234567,\n" + " 1.7976931348623157e+308,\n" +#ifdef QT_NO_DOUBLECONVERSION // "shortest" double conversion is not very short then + " 4.9406564584124654e-324,\n" + " 2.2250738585072014e-308,\n" + " 1.7976931348623157e+308,\n" + " 2.2204460492503131e-16,\n" + " 4.9406564584124654e-324,\n" + " 0,\n" + " -2.2250738585072014e-308,\n" + " -1.7976931348623157e+308,\n" + " -2.2204460492503131e-16,\n" + " -4.9406564584124654e-324,\n" +#else + " 5e-324,\n" + " 2.2250738585072014e-308,\n" + " 1.7976931348623157e+308,\n" + " 2.220446049250313e-16,\n" + " 5e-324,\n" + " 0,\n" + " -2.2250738585072014e-308,\n" + " -1.7976931348623157e+308,\n" + " -2.220446049250313e-16,\n" + " -5e-324,\n" +#endif + " 0,\n" + " 9007199254740992,\n" + " -9007199254740992\n" + " ]\n" + "}\n"; + +#ifdef Q_OS_QNX + QEXPECT_FAIL("", "See QTBUG-37066", Continue); +#endif + QCOMPARE(json, expected); + + QJsonDocument doc; + doc.setObject(object); + json = doc.toJson(); +#ifdef Q_OS_QNX + QEXPECT_FAIL("", "See QTBUG-37066", Continue); +#endif + QCOMPARE(json, expected); +} + +void tst_QtJson::fromJson() +{ + { + QByteArray json = "[\n true\n]\n"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0).type(), QJsonValue::Bool); + QCOMPARE(array.at(0).toBool(), true); + QCOMPARE(doc.toJson(), json); + } + { + //regression test: test if unicode_control_characters are correctly decoded + QByteArray json = "[\n \"" UNICODE_NON_CHARACTER "\"\n]\n"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QCOMPARE(array.at(0).type(), QJsonValue::String); + QCOMPARE(array.at(0).toString(), QString::fromUtf8(UNICODE_NON_CHARACTER)); + QCOMPARE(doc.toJson(), json); + } + { + QByteArray json = "[]"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 0); + } + { + QByteArray json = "{}"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QJsonObject object = doc.object(); + QCOMPARE(object.size(), 0); + } + { + QByteArray json = "{\n \"Key\": true\n}\n"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QJsonObject object = doc.object(); + QCOMPARE(object.size(), 1); + QCOMPARE(object.value("Key"), QJsonValue(true)); + QCOMPARE(doc.toJson(), json); + } + { + QByteArray json = "[ null, true, false, \"Foo\", 1, [], {} ]"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 7); + QCOMPARE(array.at(0).type(), QJsonValue::Null); + QCOMPARE(array.at(1).type(), QJsonValue::Bool); + QCOMPARE(array.at(1).toBool(), true); + QCOMPARE(array.at(2).type(), QJsonValue::Bool); + QCOMPARE(array.at(2).toBool(), false); + QCOMPARE(array.at(3).type(), QJsonValue::String); + QCOMPARE(array.at(3).toString(), QLatin1String("Foo")); + QCOMPARE(array.at(4).type(), QJsonValue::Double); + QCOMPARE(array.at(4).toDouble(), 1.); + QCOMPARE(array.at(5).type(), QJsonValue::Array); + QCOMPARE(array.at(5).toArray().size(), 0); + QCOMPARE(array.at(6).type(), QJsonValue::Object); + QCOMPARE(array.at(6).toObject().size(), 0); + } + { + QByteArray json = "{ \"0\": null, \"1\": true, \"2\": false, \"3\": \"Foo\", \"4\": 1, \"5\": [], \"6\": {} }"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QJsonObject object = doc.object(); + QCOMPARE(object.size(), 7); + QCOMPARE(object.value("0").type(), QJsonValue::Null); + QCOMPARE(object.value("1").type(), QJsonValue::Bool); + QCOMPARE(object.value("1").toBool(), true); + QCOMPARE(object.value("2").type(), QJsonValue::Bool); + QCOMPARE(object.value("2").toBool(), false); + QCOMPARE(object.value("3").type(), QJsonValue::String); + QCOMPARE(object.value("3").toString(), QLatin1String("Foo")); + QCOMPARE(object.value("4").type(), QJsonValue::Double); + QCOMPARE(object.value("4").toDouble(), 1.); + QCOMPARE(object.value("5").type(), QJsonValue::Array); + QCOMPARE(object.value("5").toArray().size(), 0); + QCOMPARE(object.value("6").type(), QJsonValue::Object); + QCOMPARE(object.value("6").toObject().size(), 0); + } + { + QByteArray compactJson = "{\"Array\": [true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"],\"\\\\Key\\n\": \"Value\",\"null\": null}"; + QJsonDocument doc = QJsonDocument::fromJson(compactJson); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QJsonObject object = doc.object(); + QCOMPARE(object.size(), 3); + QCOMPARE(object.value("\\Key\n").isString(), true); + QCOMPARE(object.value("\\Key\n").toString(), QString("Value")); + QCOMPARE(object.value("null").isNull(), true); + QCOMPARE(object.value("Array").isArray(), true); + QJsonArray array = object.value("Array").toArray(); + QCOMPARE(array.size(), 5); + QCOMPARE(array.at(0).isBool(), true); + QCOMPARE(array.at(0).toBool(), true); + QCOMPARE(array.at(1).isDouble(), true); + QCOMPARE(array.at(1).toDouble(), 999.); + QCOMPARE(array.at(2).isString(), true); + QCOMPARE(array.at(2).toString(), QLatin1String("string")); + QCOMPARE(array.at(3).isNull(), true); + QCOMPARE(array.at(4).isString(), true); + QCOMPARE(array.at(4).toString(), QLatin1String("\\\a\n\r\b\tabcABC\"")); + } +} + +void tst_QtJson::fromJsonErrors() +{ + { + QJsonParseError error; + QByteArray json = "{\n \n\n"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::UnterminatedObject); + QCOMPARE(error.offset, 8); + } + { + QJsonParseError error; + QByteArray json = "{\n \"key\" 10\n"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::MissingNameSeparator); + QCOMPARE(error.offset, 13); + } + { + QJsonParseError error; + QByteArray json = "[\n \n\n"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::UnterminatedArray); + QCOMPARE(error.offset, 8); + } + { + QJsonParseError error; + QByteArray json = "[\n 1, true\n\n"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::UnterminatedArray); + QCOMPARE(error.offset, 14); + } + { + QJsonParseError error; + QByteArray json = "[\n 1 true\n\n"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::MissingValueSeparator); + QCOMPARE(error.offset, 7); + } + { + QJsonParseError error; + QByteArray json = "[\n nul"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalValue); + QCOMPARE(error.offset, 7); + } + { + QJsonParseError error; + QByteArray json = "[\n nulzz"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalValue); + QCOMPARE(error.offset, 10); + } + { + QJsonParseError error; + QByteArray json = "[\n tru"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalValue); + QCOMPARE(error.offset, 7); + } + { + QJsonParseError error; + QByteArray json = "[\n trud]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalValue); + QCOMPARE(error.offset, 10); + } + { + QJsonParseError error; + QByteArray json = "[\n fal"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalValue); + QCOMPARE(error.offset, 7); + } + { + QJsonParseError error; + QByteArray json = "[\n falsd]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalValue); + QCOMPARE(error.offset, 11); + } + { + QJsonParseError error; + QByteArray json = "[\n 11111"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::TerminationByNumber); + QCOMPARE(error.offset, 11); + } + { + QJsonParseError error; + QByteArray json = "[\n -1E10000]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalNumber); + QCOMPARE(error.offset, 14); + } + { + QJsonParseError error; + QByteArray json = "[\n -1e-10000]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalNumber); + QCOMPARE(error.offset, 15); + } + { + QJsonParseError error; + QByteArray json = "[\n \"\\u12\"]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalEscapeSequence); + QCOMPARE(error.offset, 11); + } + { + QJsonParseError error; + QByteArray json = "[\n \"foo" INVALID_UNICODE "bar\"]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalUTF8String); + QCOMPARE(error.offset, 12); + } + { + QJsonParseError error; + QByteArray json = "[\n \""; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::UnterminatedString); + QCOMPARE(error.offset, 8); + } + { + QJsonParseError error; + QByteArray json = "[\n \"c" UNICODE_DJE "a\\u12\"]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalEscapeSequence); + QCOMPARE(error.offset, 15); + } + { + QJsonParseError error; + QByteArray json = "[\n \"c" UNICODE_DJE "a" INVALID_UNICODE "bar\"]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::IllegalUTF8String); + QCOMPARE(error.offset, 13); + } + { + QJsonParseError error; + QByteArray json = "[\n \"c" UNICODE_DJE "a ]"; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QVERIFY(doc.isEmpty()); + QCOMPARE(error.error, QJsonParseError::UnterminatedString); + QCOMPARE(error.offset, 14); + } +} + +void tst_QtJson::fromBinary() +{ + QFile file(testDataDir + "/test.json"); + file.open(QFile::ReadOnly); + QByteArray testJson = file.readAll(); + + QJsonDocument doc = QJsonDocument::fromJson(testJson); + QJsonDocument outdoc = QJsonDocument::fromBinaryData(doc.toBinaryData()); + QVERIFY(!outdoc.isNull()); + QCOMPARE(doc, outdoc); + + QFile bfile(testDataDir + "/test.bjson"); + bfile.open(QFile::ReadOnly); + QByteArray binary = bfile.readAll(); + + QJsonDocument bdoc = QJsonDocument::fromBinaryData(binary); + QVERIFY(!bdoc.isNull()); + QCOMPARE(doc.toVariant(), bdoc.toVariant()); + QCOMPARE(doc, bdoc); +} + +void tst_QtJson::toAndFromBinary_data() +{ + QTest::addColumn<QString>("filename"); + QTest::newRow("test.json") << (testDataDir + "/test.json"); + QTest::newRow("test2.json") << (testDataDir + "/test2.json"); +} + +void tst_QtJson::toAndFromBinary() +{ + QFETCH(QString, filename); + QFile file(filename); + QVERIFY(file.open(QFile::ReadOnly)); + QByteArray data = file.readAll(); + + QJsonDocument doc = QJsonDocument::fromJson(data); + QVERIFY(!doc.isNull()); + QJsonDocument outdoc = QJsonDocument::fromBinaryData(doc.toBinaryData()); + QVERIFY(!outdoc.isNull()); + QCOMPARE(doc, outdoc); +} + +void tst_QtJson::invalidBinaryData() +{ + QDir dir(testDataDir + "/invalidBinaryData"); + QFileInfoList files = dir.entryInfoList(); + for (int i = 0; i < files.size(); ++i) { + if (!files.at(i).isFile()) + continue; + QFile file(files.at(i).filePath()); + file.open(QIODevice::ReadOnly); + QByteArray bytes = file.readAll(); + QJsonDocument document = QJsonDocument::fromRawData(bytes.constData(), bytes.size()); + QVERIFY(document.isNull()); + } +} + +void tst_QtJson::parseNumbers() +{ + { + // test number parsing + struct Numbers { + const char *str; + int n; + }; + Numbers numbers [] = { + { "0", 0 }, + { "1", 1 }, + { "10", 10 }, + { "-1", -1 }, + { "100000", 100000 }, + { "-999", -999 } + }; + int size = sizeof(numbers)/sizeof(Numbers); + for (int i = 0; i < size; ++i) { + QByteArray json = "[ "; + json += numbers[i].str; + json += " ]"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QJsonValue val = array.at(0); + QCOMPARE(val.type(), QJsonValue::Double); + QCOMPARE(val.toDouble(), (double)numbers[i].n); + } + } + { + // test number parsing + struct Numbers { + const char *str; + double n; + }; + Numbers numbers [] = { + { "0", 0 }, + { "1", 1 }, + { "10", 10 }, + { "-1", -1 }, + { "100000", 100000 }, + { "-999", -999 }, + { "1.1", 1.1 }, + { "1e10", 1e10 }, + { "-1.1", -1.1 }, + { "-1e10", -1e10 }, + { "-1E10", -1e10 }, + { "1.1e10", 1.1e10 }, + { "1.1e308", 1.1e308 }, + { "-1.1e308", -1.1e308 }, + { "1.1e-308", 1.1e-308 }, + { "-1.1e-308", -1.1e-308 }, + { "1.1e+308", 1.1e+308 }, + { "-1.1e+308", -1.1e+308 }, + { "1.e+308", 1.e+308 }, + { "-1.e+308", -1.e+308 } + }; + int size = sizeof(numbers)/sizeof(Numbers); + for (int i = 0; i < size; ++i) { + QByteArray json = "[ "; + json += numbers[i].str; + json += " ]"; + QJsonDocument doc = QJsonDocument::fromJson(json); +#ifdef Q_OS_QNX + if (0 == QString::compare(numbers[i].str, "1.1e-308")) + QEXPECT_FAIL("", "See QTBUG-37066", Abort); +#endif + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QJsonValue val = array.at(0); + QCOMPARE(val.type(), QJsonValue::Double); + QCOMPARE(val.toDouble(), numbers[i].n); + } + } +} + +void tst_QtJson::parseStrings() +{ + const char *strings [] = + { + "Foo", + "abc\\\"abc", + "abc\\\\abc", + "abc\\babc", + "abc\\fabc", + "abc\\nabc", + "abc\\rabc", + "abc\\tabc", + "abc\\u0019abc", + "abc" UNICODE_DJE "abc", + UNICODE_NON_CHARACTER + }; + int size = sizeof(strings)/sizeof(const char *); + + for (int i = 0; i < size; ++i) { + QByteArray json = "[\n \""; + json += strings[i]; + json += "\"\n]\n"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QJsonValue val = array.at(0); + QCOMPARE(val.type(), QJsonValue::String); + + QCOMPARE(doc.toJson(), json); + } + + struct Pairs { + const char *in; + const char *out; + }; + Pairs pairs [] = { + { "abc\\/abc", "abc/abc" }, + { "abc\\u0402abc", "abc" UNICODE_DJE "abc" }, + { "abc\\u0065abc", "abceabc" }, + { "abc\\uFFFFabc", "abc" UNICODE_NON_CHARACTER "abc" } + }; + size = sizeof(pairs)/sizeof(Pairs); + + for (int i = 0; i < size; ++i) { + QByteArray json = "[\n \""; + json += pairs[i].in; + json += "\"\n]\n"; + QByteArray out = "[\n \""; + out += pairs[i].out; + out += "\"\n]\n"; + QJsonDocument doc = QJsonDocument::fromJson(json); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), true); + QCOMPARE(doc.isObject(), false); + QJsonArray array = doc.array(); + QCOMPARE(array.size(), 1); + QJsonValue val = array.at(0); + QCOMPARE(val.type(), QJsonValue::String); + + QCOMPARE(doc.toJson(), out); + } + +} + +void tst_QtJson::parseDuplicateKeys() +{ + const char *json = "{ \"B\": true, \"A\": null, \"B\": false }"; + + QJsonDocument doc = QJsonDocument::fromJson(json); + QCOMPARE(doc.isObject(), true); + + QJsonObject o = doc.object(); + QCOMPARE(o.size(), 2); + QJsonObject::const_iterator it = o.constBegin(); + QCOMPARE(it.key(), QLatin1String("A")); + QCOMPARE(it.value(), QJsonValue()); + ++it; + QCOMPARE(it.key(), QLatin1String("B")); + QCOMPARE(it.value(), QJsonValue(false)); +} + +void tst_QtJson::testParser() +{ + QFile file(testDataDir + "/test.json"); + file.open(QFile::ReadOnly); + QByteArray testJson = file.readAll(); + + QJsonDocument doc = QJsonDocument::fromJson(testJson); + QVERIFY(!doc.isEmpty()); +} + +void tst_QtJson::compactArray() +{ + QJsonArray array; + array.append(QLatin1String("First Entry")); + array.append(QLatin1String("Second Entry")); + array.append(QLatin1String("Third Entry")); + QJsonDocument doc(array); + int s = doc.toBinaryData().size(); + array.removeAt(1); + doc.setArray(array); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + QByteArray("[\n" + " \"First Entry\",\n" + " \"Third Entry\"\n" + "]\n")); + + array.removeAt(0); + doc.setArray(array); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + QByteArray("[\n" + " \"Third Entry\"\n" + "]\n")); + + array.removeAt(0); + doc.setArray(array); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + QByteArray("[\n" + "]\n")); + +} + +void tst_QtJson::compactObject() +{ + QJsonObject object; + object.insert(QLatin1String("Key1"), QLatin1String("First Entry")); + object.insert(QLatin1String("Key2"), QLatin1String("Second Entry")); + object.insert(QLatin1String("Key3"), QLatin1String("Third Entry")); + QJsonDocument doc(object); + int s = doc.toBinaryData().size(); + object.remove(QLatin1String("Key2")); + doc.setObject(object); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + QByteArray("{\n" + " \"Key1\": \"First Entry\",\n" + " \"Key3\": \"Third Entry\"\n" + "}\n")); + + object.remove(QLatin1String("Key1")); + doc.setObject(object); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + QByteArray("{\n" + " \"Key3\": \"Third Entry\"\n" + "}\n")); + + object.remove(QLatin1String("Key3")); + doc.setObject(object); + QVERIFY(s > doc.toBinaryData().size()); + s = doc.toBinaryData().size(); + QCOMPARE(doc.toJson(), + QByteArray("{\n" + "}\n")); + +} + +void tst_QtJson::validation() +{ + // this basically tests that we don't crash on corrupt data + QFile file(testDataDir + "/test.json"); + QVERIFY(file.open(QFile::ReadOnly)); + QByteArray testJson = file.readAll(); + QVERIFY(!testJson.isEmpty()); + + QJsonDocument doc = QJsonDocument::fromJson(testJson); + QVERIFY(!doc.isNull()); + + QByteArray binary = doc.toBinaryData(); + + // only test the first 1000 bytes. Testing the full file takes too long + for (int i = 0; i < 1000; ++i) { + QByteArray corrupted = binary; + corrupted[i] = char(0xff); + QJsonDocument doc = QJsonDocument::fromBinaryData(corrupted); + if (doc.isNull()) + continue; + QByteArray json = doc.toJson(); + } + + + QFile file2(testDataDir + "/test3.json"); + file2.open(QFile::ReadOnly); + testJson = file2.readAll(); + QVERIFY(!testJson.isEmpty()); + + doc = QJsonDocument::fromJson(testJson); + QVERIFY(!doc.isNull()); + + binary = doc.toBinaryData(); + + for (int i = 0; i < binary.size(); ++i) { + QByteArray corrupted = binary; + corrupted[i] = char(0xff); + QJsonDocument doc = QJsonDocument::fromBinaryData(corrupted); + if (doc.isNull()) + continue; + QByteArray json = doc.toJson(); + + corrupted = binary; + corrupted[i] = 0x00; + doc = QJsonDocument::fromBinaryData(corrupted); + if (doc.isNull()) + continue; + json = doc.toJson(); + } +} + +void tst_QtJson::assignToDocument() +{ + { + const char *json = "{ \"inner\": { \"key\": true } }"; + QJsonDocument doc = QJsonDocument::fromJson(json); + + QJsonObject o = doc.object(); + QJsonValue inner = o.value("inner"); + + QJsonDocument innerDoc(inner.toObject()); + + QVERIFY(innerDoc != doc); + QCOMPARE(innerDoc.object(), inner.toObject()); + } + { + const char *json = "[ [ true ] ]"; + QJsonDocument doc = QJsonDocument::fromJson(json); + + QJsonArray a = doc.array(); + QJsonValue inner = a.at(0); + + QJsonDocument innerDoc(inner.toArray()); + + QVERIFY(innerDoc != doc); + QCOMPARE(innerDoc.array(), inner.toArray()); + } +} + + +void tst_QtJson::testDuplicateKeys() +{ + QJsonObject obj; + obj.insert(QLatin1String("foo"), QLatin1String("bar")); + obj.insert(QLatin1String("foo"), QLatin1String("zap")); + QCOMPARE(obj.size(), 1); + QCOMPARE(obj.value(QLatin1String("foo")).toString(), QLatin1String("zap")); +} + +void tst_QtJson::testCompaction() +{ + // modify object enough times to trigger compactionCounter + // and make sure the data is still valid + QJsonObject obj; + for (int i = 0; i < 33; ++i) { + obj.remove(QLatin1String("foo")); + obj.insert(QLatin1String("foo"), QLatin1String("bar")); + } + QCOMPARE(obj.size(), 1); + QCOMPARE(obj.value(QLatin1String("foo")).toString(), QLatin1String("bar")); + + QJsonDocument doc = QJsonDocument::fromBinaryData(QJsonDocument(obj).toBinaryData()); + QVERIFY(!doc.isNull()); + QVERIFY(!doc.isEmpty()); + QCOMPARE(doc.isArray(), false); + QCOMPARE(doc.isObject(), true); + QCOMPARE(doc.object(), obj); +} + +void tst_QtJson::testDebugStream() +{ + { + // QJsonObject + + QJsonObject object; + QTest::ignoreMessage(QtDebugMsg, "QJsonObject()"); + qDebug() << object; + + object.insert(QLatin1String("foo"), QLatin1String("bar")); + QTest::ignoreMessage(QtDebugMsg, "QJsonObject({\"foo\":\"bar\"})"); + qDebug() << object; + } + + { + // QJsonArray + + QJsonArray array; + QTest::ignoreMessage(QtDebugMsg, "QJsonArray()"); + qDebug() << array; + + array.append(1); + array.append(QLatin1String("foo")); + QTest::ignoreMessage(QtDebugMsg, "QJsonArray([1,\"foo\"])"); + qDebug() << array; + } + + { + // QJsonDocument + + QJsonDocument doc; + QTest::ignoreMessage(QtDebugMsg, "QJsonDocument()"); + qDebug() << doc; + + QJsonObject object; + object.insert(QLatin1String("foo"), QLatin1String("bar")); + doc.setObject(object); + QTest::ignoreMessage(QtDebugMsg, "QJsonDocument({\"foo\":\"bar\"})"); + qDebug() << doc; + + QJsonArray array; + array.append(1); + array.append(QLatin1String("foo")); + QTest::ignoreMessage(QtDebugMsg, "QJsonDocument([1,\"foo\"])"); + doc.setArray(array); + qDebug() << doc; + } + + { + // QJsonValue + + QJsonValue value; + + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(null)"); + qDebug() << value; + + value = QJsonValue(true); // bool + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(bool, true)"); + qDebug() << value; + + value = QJsonValue((double)4.2); // double + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(double, 4.2)"); + qDebug() << value; + + value = QJsonValue((int)42); // int + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(double, 42)"); + qDebug() << value; + + value = QJsonValue(QLatin1String("foo")); // string + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(string, \"foo\")"); + qDebug() << value; + + QJsonArray array; + array.append(1); + array.append(QLatin1String("foo")); + value = QJsonValue(array); // array + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(array, QJsonArray([1,\"foo\"]))"); + qDebug() << value; + + QJsonObject object; + object.insert(QLatin1String("foo"), QLatin1String("bar")); + value = QJsonValue(object); // object + QTest::ignoreMessage(QtDebugMsg, "QJsonValue(object, QJsonObject({\"foo\":\"bar\"}))"); + qDebug() << value; + } +} + +void tst_QtJson::testCompactionError() +{ + QJsonObject schemaObject; + schemaObject.insert("_Type", QLatin1String("_SchemaType")); + schemaObject.insert("name", QLatin1String("Address")); + schemaObject.insert("schema", QJsonObject()); + { + QJsonObject content(schemaObject); + QJsonDocument doc(content); + QVERIFY(!doc.isNull()); + QByteArray hash = QCryptographicHash::hash(doc.toBinaryData(), QCryptographicHash::Md5).toHex(); + schemaObject.insert("_Version", QString::fromLatin1(hash.constData(), hash.size())); + } + + QJsonObject schema; + schema.insert("streetNumber", schema.value("number").toObject()); + schemaObject.insert("schema", schema); + { + QJsonObject content(schemaObject); + content.remove("_Uuid"); + content.remove("_Version"); + QJsonDocument doc(content); + QVERIFY(!doc.isNull()); + QByteArray hash = QCryptographicHash::hash(doc.toBinaryData(), QCryptographicHash::Md5).toHex(); + schemaObject.insert("_Version", QString::fromLatin1(hash.constData(), hash.size())); + } +} + +void tst_QtJson::parseUnicodeEscapes() +{ + const QByteArray json = "[ \"A\\u00e4\\u00C4\" ]"; + + QJsonDocument doc = QJsonDocument::fromJson(json); + QJsonArray array = doc.array(); + + QString result = QLatin1String("A"); + result += QChar(0xe4); + result += QChar(0xc4); + + QCOMPARE(array.first().toString(), result); +} + +void tst_QtJson::assignObjects() +{ + const char *json = + "[ { \"Key\": 1 }, { \"Key\": 2 } ]"; + + QJsonDocument doc = QJsonDocument::fromJson(json); + QJsonArray array = doc.array(); + + QJsonObject object = array.at(0).toObject(); + QCOMPARE(object.value("Key").toDouble(), 1.); + + object = array.at(1).toObject(); + QCOMPARE(object.value("Key").toDouble(), 2.); +} + +void tst_QtJson::assignArrays() +{ + const char *json = + "[ [ 1 ], [ 2 ] ]"; + + QJsonDocument doc = QJsonDocument::fromJson(json); + QJsonArray array = doc.array(); + + QJsonArray inner = array.at(0).toArray() ; + QCOMPARE(inner.at(0).toDouble(), 1.); + + inner= array.at(1).toArray(); + QCOMPARE(inner.at(0).toDouble(), 2.); +} + +void tst_QtJson::testTrailingComma() +{ + const char *jsons[] = { "{ \"Key\": 1, }", "[ { \"Key\": 1 }, ]" }; + + for (unsigned i = 0; i < sizeof(jsons)/sizeof(jsons[0]); ++i) { + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsons[i], &error); + QCOMPARE(error.error, QJsonParseError::MissingObject); + } +} + +void tst_QtJson::testDetachBug() +{ + QJsonObject dynamic; + QJsonObject embedded; + + QJsonObject local; + + embedded.insert("Key1", QString("Value1")); + embedded.insert("Key2", QString("Value2")); + dynamic.insert(QStringLiteral("Bogus"), QString("bogusValue")); + dynamic.insert("embedded", embedded); + local = dynamic.value("embedded").toObject(); + + dynamic.remove("embedded"); + + QCOMPARE(local.keys().size(),2); + local.remove("Key1"); + local.remove("Key2"); + QCOMPARE(local.keys().size(), 0); + + local.insert("Key1", QString("anotherValue")); + QCOMPARE(local.keys().size(), 1); +} + +void tst_QtJson::valueEquals() +{ + QCOMPARE(QJsonValue(), QJsonValue()); + QVERIFY(QJsonValue() != QJsonValue(QJsonValue::Undefined)); + QVERIFY(QJsonValue() != QJsonValue(true)); + QVERIFY(QJsonValue() != QJsonValue(1.)); + QVERIFY(QJsonValue() != QJsonValue(QJsonArray())); + QVERIFY(QJsonValue() != QJsonValue(QJsonObject())); + + QCOMPARE(QJsonValue(true), QJsonValue(true)); + QVERIFY(QJsonValue(true) != QJsonValue(false)); + QVERIFY(QJsonValue(true) != QJsonValue(QJsonValue::Undefined)); + QVERIFY(QJsonValue(true) != QJsonValue()); + QVERIFY(QJsonValue(true) != QJsonValue(1.)); + QVERIFY(QJsonValue(true) != QJsonValue(QJsonArray())); + QVERIFY(QJsonValue(true) != QJsonValue(QJsonObject())); + + QCOMPARE(QJsonValue(1), QJsonValue(1)); + QVERIFY(QJsonValue(1) != QJsonValue(2)); + QCOMPARE(QJsonValue(1), QJsonValue(1.)); + QVERIFY(QJsonValue(1) != QJsonValue(1.1)); + QVERIFY(QJsonValue(1) != QJsonValue(QJsonValue::Undefined)); + QVERIFY(QJsonValue(1) != QJsonValue()); + QVERIFY(QJsonValue(1) != QJsonValue(true)); + QVERIFY(QJsonValue(1) != QJsonValue(QJsonArray())); + QVERIFY(QJsonValue(1) != QJsonValue(QJsonObject())); + + QCOMPARE(QJsonValue(1.), QJsonValue(1.)); + QVERIFY(QJsonValue(1.) != QJsonValue(2.)); + QVERIFY(QJsonValue(1.) != QJsonValue(QJsonValue::Undefined)); + QVERIFY(QJsonValue(1.) != QJsonValue()); + QVERIFY(QJsonValue(1.) != QJsonValue(true)); + QVERIFY(QJsonValue(1.) != QJsonValue(QJsonArray())); + QVERIFY(QJsonValue(1.) != QJsonValue(QJsonObject())); + + QCOMPARE(QJsonValue(QJsonArray()), QJsonValue(QJsonArray())); + QJsonArray nonEmptyArray; + nonEmptyArray.append(true); + QVERIFY(QJsonValue(QJsonArray()) != nonEmptyArray); + QVERIFY(QJsonValue(QJsonArray()) != QJsonValue(QJsonValue::Undefined)); + QVERIFY(QJsonValue(QJsonArray()) != QJsonValue()); + QVERIFY(QJsonValue(QJsonArray()) != QJsonValue(true)); + QVERIFY(QJsonValue(QJsonArray()) != QJsonValue(1.)); + QVERIFY(QJsonValue(QJsonArray()) != QJsonValue(QJsonObject())); + + QCOMPARE(QJsonValue(QJsonObject()), QJsonValue(QJsonObject())); + QJsonObject nonEmptyObject; + nonEmptyObject.insert("Key", true); + QVERIFY(QJsonValue(QJsonObject()) != nonEmptyObject); + QVERIFY(QJsonValue(QJsonObject()) != QJsonValue(QJsonValue::Undefined)); + QVERIFY(QJsonValue(QJsonObject()) != QJsonValue()); + QVERIFY(QJsonValue(QJsonObject()) != QJsonValue(true)); + QVERIFY(QJsonValue(QJsonObject()) != QJsonValue(1.)); + QVERIFY(QJsonValue(QJsonObject()) != QJsonValue(QJsonArray())); + + QCOMPARE(QJsonValue("foo"), QJsonValue(QLatin1String("foo"))); + QCOMPARE(QJsonValue("foo"), QJsonValue(QString("foo"))); + QCOMPARE(QJsonValue("\x66\x6f\x6f"), QJsonValue(QString("foo"))); + QCOMPARE(QJsonValue("\x62\x61\x72"), QJsonValue("bar")); + QCOMPARE(QJsonValue(UNICODE_NON_CHARACTER), QJsonValue(QString(UNICODE_NON_CHARACTER))); + QCOMPARE(QJsonValue(UNICODE_DJE), QJsonValue(QString(UNICODE_DJE))); + QCOMPARE(QJsonValue("\xc3\xa9"), QJsonValue(QString("\xc3\xa9"))); +} + +void tst_QtJson::objectEquals_data() +{ + QTest::addColumn<QJsonObject>("left"); + QTest::addColumn<QJsonObject>("right"); + QTest::addColumn<bool>("result"); + + QTest::newRow("two defaults") << QJsonObject() << QJsonObject() << true; + + QJsonObject object1; + object1.insert("property", 1); + QJsonObject object2; + object2["property"] = 1; + QJsonObject object3; + object3.insert("property1", 1); + object3.insert("property2", 2); + + QTest::newRow("the same object (1 vs 2)") << object1 << object2 << true; + QTest::newRow("the same object (3 vs 3)") << object3 << object3 << true; + QTest::newRow("different objects (2 vs 3)") << object2 << object3 << false; + QTest::newRow("object vs default") << object1 << QJsonObject() << false; + + QJsonObject empty; + empty.insert("property", 1); + empty.take("property"); + QTest::newRow("default vs empty") << QJsonObject() << empty << true; + QTest::newRow("empty vs empty") << empty << empty << true; + QTest::newRow("object vs empty") << object1 << empty << false; + + QJsonObject referencedEmpty; + referencedEmpty["undefined"]; + QTest::newRow("referenced empty vs referenced empty") << referencedEmpty << referencedEmpty << true; + QTest::newRow("referenced empty vs object") << referencedEmpty << object1 << false; + + QJsonObject referencedObject1; + referencedObject1.insert("property", 1); + referencedObject1["undefined"]; + QJsonObject referencedObject2; + referencedObject2.insert("property", 1); + referencedObject2["aaaaaaaaa"]; // earlier then "property" + referencedObject2["zzzzzzzzz"]; // after "property" + QTest::newRow("referenced object vs default") << referencedObject1 << QJsonObject() << false; + QTest::newRow("referenced object vs referenced object") << referencedObject1 << referencedObject1 << true; + QTest::newRow("referenced object vs object (different)") << referencedObject1 << object3 << false; +} + +void tst_QtJson::objectEquals() +{ + QFETCH(QJsonObject, left); + QFETCH(QJsonObject, right); + QFETCH(bool, result); + + QCOMPARE(left == right, result); + QCOMPARE(right == left, result); + + // invariants checks + QCOMPARE(left, left); + QCOMPARE(right, right); + QCOMPARE(left != right, !result); + QCOMPARE(right != left, !result); + + // The same but from QJsonValue perspective + QCOMPARE(QJsonValue(left) == QJsonValue(right), result); + QCOMPARE(QJsonValue(left) != QJsonValue(right), !result); + QCOMPARE(QJsonValue(right) == QJsonValue(left), result); + QCOMPARE(QJsonValue(right) != QJsonValue(left), !result); +} + +void tst_QtJson::arrayEquals_data() +{ + QTest::addColumn<QJsonArray>("left"); + QTest::addColumn<QJsonArray>("right"); + QTest::addColumn<bool>("result"); + + QTest::newRow("two defaults") << QJsonArray() << QJsonArray() << true; + + QJsonArray array1; + array1.append(1); + QJsonArray array2; + array2.append(2111); + array2[0] = 1; + QJsonArray array3; + array3.insert(0, 1); + array3.insert(1, 2); + + QTest::newRow("the same array (1 vs 2)") << array1 << array2 << true; + QTest::newRow("the same array (3 vs 3)") << array3 << array3 << true; + QTest::newRow("different arrays (2 vs 3)") << array2 << array3 << false; + QTest::newRow("array vs default") << array1 << QJsonArray() << false; + + QJsonArray empty; + empty.append(1); + empty.takeAt(0); + QTest::newRow("default vs empty") << QJsonArray() << empty << true; + QTest::newRow("empty vs default") << empty << QJsonArray() << true; + QTest::newRow("empty vs empty") << empty << empty << true; + QTest::newRow("array vs empty") << array1 << empty << false; +} + +void tst_QtJson::arrayEquals() +{ + QFETCH(QJsonArray, left); + QFETCH(QJsonArray, right); + QFETCH(bool, result); + + QCOMPARE(left == right, result); + QCOMPARE(right == left, result); + + // invariants checks + QCOMPARE(left, left); + QCOMPARE(right, right); + QCOMPARE(left != right, !result); + QCOMPARE(right != left, !result); + + // The same but from QJsonValue perspective + QCOMPARE(QJsonValue(left) == QJsonValue(right), result); + QCOMPARE(QJsonValue(left) != QJsonValue(right), !result); + QCOMPARE(QJsonValue(right) == QJsonValue(left), result); + QCOMPARE(QJsonValue(right) != QJsonValue(left), !result); +} + +void tst_QtJson::bom() +{ + QFile file(testDataDir + "/bom.json"); + file.open(QFile::ReadOnly); + QByteArray json = file.readAll(); + + // Import json document into a QJsonDocument + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + + QVERIFY(!doc.isNull()); + QCOMPARE(error.error, QJsonParseError::NoError); +} + +void tst_QtJson::nesting() +{ + // check that we abort parsing too deeply nested json documents. + // this is to make sure we don't crash because the parser exhausts the + // stack. + + const char *array_data = + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"; + + QByteArray json(array_data); + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + + QVERIFY(!doc.isNull()); + QCOMPARE(error.error, QJsonParseError::NoError); + + json.prepend('['); + json.append(']'); + doc = QJsonDocument::fromJson(json, &error); + + QVERIFY(doc.isNull()); + QCOMPARE(error.error, QJsonParseError::DeepNesting); + + json = QByteArray("true "); + + for (int i = 0; i < 1024; ++i) { + json.prepend("{ \"Key\": "); + json.append(" }"); + } + + doc = QJsonDocument::fromJson(json, &error); + + QVERIFY(!doc.isNull()); + QCOMPARE(error.error, QJsonParseError::NoError); + + json.prepend('['); + json.append(']'); + doc = QJsonDocument::fromJson(json, &error); + + QVERIFY(doc.isNull()); + QCOMPARE(error.error, QJsonParseError::DeepNesting); + +} + +void tst_QtJson::longStrings() +{ + // test around 15 and 16 bit boundaries, as these are limits + // in the data structures (for Latin1String in qjson_p.h) + QString s(0x7ff0, 'a'); + for (int i = 0x7ff0; i < 0x8010; i++) { + s.append(QLatin1Char('c')); + + QMap <QString, QVariant> map; + map["key"] = s; + + /* Create a QJsonDocument from the QMap ... */ + QJsonDocument d1 = QJsonDocument::fromVariant(QVariant(map)); + /* ... and a QByteArray from the QJsonDocument */ + QByteArray a1 = d1.toJson(); + + /* Create a QJsonDocument from the QByteArray ... */ + QJsonDocument d2 = QJsonDocument::fromJson(a1); + /* ... and a QByteArray from the QJsonDocument */ + QByteArray a2 = d2.toJson(); + QCOMPARE(a1, a2); + } + + s = QString(0xfff0, 'a'); + for (int i = 0xfff0; i < 0x10010; i++) { + s.append(QLatin1Char('c')); + + QMap <QString, QVariant> map; + map["key"] = s; + + /* Create a QJsonDocument from the QMap ... */ + QJsonDocument d1 = QJsonDocument::fromVariant(QVariant(map)); + /* ... and a QByteArray from the QJsonDocument */ + QByteArray a1 = d1.toJson(); + + /* Create a QJsonDocument from the QByteArray ... */ + QJsonDocument d2 = QJsonDocument::fromJson(a1); + /* ... and a QByteArray from the QJsonDocument */ + QByteArray a2 = d2.toJson(); + QCOMPARE(a1, a2); + } +} + +void tst_QtJson::testJsonValueRefDefault() +{ + QJsonObject empty; + + QCOMPARE(empty["n/a"].toString(), QString()); + QCOMPARE(empty["n/a"].toString("default"), QStringLiteral("default")); + + QCOMPARE(empty["n/a"].toBool(), false); + QCOMPARE(empty["n/a"].toBool(true), true); + + QCOMPARE(empty["n/a"].toInt(), 0); + QCOMPARE(empty["n/a"].toInt(42), 42); + + QCOMPARE(empty["n/a"].toDouble(), 0.0); + QCOMPARE(empty["n/a"].toDouble(42.0), 42.0); +} + +void tst_QtJson::arrayInitializerList() +{ +#ifndef Q_COMPILER_INITIALIZER_LISTS + QSKIP("initializer_list is enabled only with c++11 support"); +#else + QVERIFY(QJsonArray{}.isEmpty()); + QCOMPARE(QJsonArray{"one"}.count(), 1); + QCOMPARE(QJsonArray{1}.count(), 1); + + { + QJsonArray a{1.3, "hello", 0}; + QCOMPARE(QJsonValue(a[0]), QJsonValue(1.3)); + QCOMPARE(QJsonValue(a[1]), QJsonValue("hello")); + QCOMPARE(QJsonValue(a[2]), QJsonValue(0)); + QCOMPARE(a.count(), 3); + } + { + QJsonObject o; + o["property"] = 1; + QJsonArray a1 {o}; + QCOMPARE(a1.count(), 1); + QCOMPARE(a1[0].toObject(), o); + + QJsonArray a2 {o, 23}; + QCOMPARE(a2.count(), 2); + QCOMPARE(a2[0].toObject(), o); + QCOMPARE(QJsonValue(a2[1]), QJsonValue(23)); + + QJsonArray a3 { a1, o, a2 }; + QCOMPARE(QJsonValue(a3[0]), QJsonValue(a1)); + QCOMPARE(QJsonValue(a3[1]), QJsonValue(o)); + QCOMPARE(QJsonValue(a3[2]), QJsonValue(a2)); + + QJsonArray a4 { 1, QJsonArray{1,2,3}, QJsonArray{"hello", 2}, QJsonObject{{"one", 1}} }; + QCOMPARE(a4.count(), 4); + QCOMPARE(QJsonValue(a4[0]), QJsonValue(1)); + + { + QJsonArray a41 = a4[1].toArray(); + QJsonArray a42 = a4[2].toArray(); + QJsonObject a43 = a4[3].toObject(); + QCOMPARE(a41.count(), 3); + QCOMPARE(a42.count(), 2); + QCOMPARE(a43.count(), 1); + + QCOMPARE(QJsonValue(a41[2]), QJsonValue(3)); + QCOMPARE(QJsonValue(a42[1]), QJsonValue(2)); + QCOMPARE(QJsonValue(a43["one"]), QJsonValue(1)); + } + } +#endif +} + +void tst_QtJson::objectInitializerList() +{ +#ifndef Q_COMPILER_INITIALIZER_LISTS + QSKIP("initializer_list is enabled only with c++11 support"); +#else + QVERIFY(QJsonObject{}.isEmpty()); + + { // one property + QJsonObject one {{"one", 1}}; + QCOMPARE(one.count(), 1); + QVERIFY(one.contains("one")); + QCOMPARE(QJsonValue(one["one"]), QJsonValue(1)); + } + { // two properties + QJsonObject two { + {"one", 1}, + {"two", 2} + }; + QCOMPARE(two.count(), 2); + QVERIFY(two.contains("one")); + QVERIFY(two.contains("two")); + QCOMPARE(QJsonValue(two["one"]), QJsonValue(1)); + QCOMPARE(QJsonValue(two["two"]), QJsonValue(2)); + } + { // nested object + QJsonObject object{{"nested", QJsonObject{{"innerProperty", 2}}}}; + QCOMPARE(object.count(), 1); + QVERIFY(object.contains("nested")); + QVERIFY(object["nested"].isObject()); + + QJsonObject nested = object["nested"].toObject(); + QCOMPARE(QJsonValue(nested["innerProperty"]), QJsonValue(2)); + } + { // nested array + QJsonObject object{{"nested", QJsonArray{"innerValue", 2.1, "bum cyk cyk"}}}; + QCOMPARE(object.count(), 1); + QVERIFY(object.contains("nested")); + QVERIFY(object["nested"].isArray()); + + QJsonArray nested = object["nested"].toArray(); + QCOMPARE(nested.count(), 3); + QCOMPARE(QJsonValue(nested[0]), QJsonValue("innerValue")); + QCOMPARE(QJsonValue(nested[1]), QJsonValue(2.1)); + } +#endif +} + +void tst_QtJson::unicodeKeys() +{ + QByteArray json = "{" + "\"x\\u2090_1\": \"hello_1\"," + "\"y\\u2090_2\": \"hello_2\"," + "\"T\\u2090_3\": \"hello_3\"," + "\"xyz_4\": \"hello_4\"," + "\"abc_5\": \"hello_5\"" + "}"; + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(json, &error); + QCOMPARE(error.error, QJsonParseError::NoError); + QJsonObject o = doc.object(); + + const auto keys = o.keys(); + QCOMPARE(keys.size(), 5); + for (const QString &key : keys) { + QString suffix = key.mid(key.indexOf(QLatin1Char('_'))); + QCOMPARE(o[key].toString(), QString("hello") + suffix); + } +} + +void tst_QtJson::garbageAtEnd() +{ + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson("{},", &error); + QCOMPARE(error.error, QJsonParseError::GarbageAtEnd); + QCOMPARE(error.offset, 2); + QVERIFY(doc.isEmpty()); + + doc = QJsonDocument::fromJson("{} ", &error); + QCOMPARE(error.error, QJsonParseError::NoError); + QVERIFY(!doc.isEmpty()); +} + +void tst_QtJson::removeNonLatinKey() +{ + const QString nonLatinKeyName = QString::fromUtf8("Атрибут100500"); + + QJsonObject sourceObject; + + sourceObject.insert("code", 1); + sourceObject.remove("code"); + + sourceObject.insert(nonLatinKeyName, 1); + + const QByteArray json = QJsonDocument(sourceObject).toJson(); + const QJsonObject restoredObject = QJsonDocument::fromJson(json).object(); + + QCOMPARE(sourceObject.keys(), restoredObject.keys()); + QVERIFY(sourceObject.contains(nonLatinKeyName)); + QVERIFY(restoredObject.contains(nonLatinKeyName)); +} + +void tst_QtJson::documentFromVariant() +{ + // Test the valid forms of QJsonDocument::fromVariant. + + QString string = QStringLiteral("value"); + + QStringList strList; + strList.append(string); + + QJsonDocument da1 = QJsonDocument::fromVariant(QVariant(strList)); + QVERIFY(da1.isArray()); + + QVariantList list; + list.append(string); + + QJsonDocument da2 = QJsonDocument::fromVariant(list); + QVERIFY(da2.isArray()); + + // As JSON arrays they should be equal. + QCOMPARE(da1.array(), da2.array()); + + + QMap <QString, QVariant> map; + map["key"] = string; + + QJsonDocument do1 = QJsonDocument::fromVariant(QVariant(map)); + QVERIFY(do1.isObject()); + + QHash <QString, QVariant> hash; + hash["key"] = string; + + QJsonDocument do2 = QJsonDocument::fromVariant(QVariant(hash)); + QVERIFY(do2.isObject()); + + // As JSON objects they should be equal. + QCOMPARE(do1.object(), do2.object()); +} + +void tst_QtJson::parseErrorOffset_data() +{ + QTest::addColumn<QByteArray>("json"); + QTest::addColumn<int>("errorOffset"); + + QTest::newRow("Trailing comma in object") << QByteArray("{ \"value\": false, }") << 19; + QTest::newRow("Trailing comma in object plus whitespace") << QByteArray("{ \"value\": false, } ") << 19; + QTest::newRow("Trailing comma in array") << QByteArray("[ false, ]") << 10; + QTest::newRow("Trailing comma in array plus whitespace") << QByteArray("[ false, ] ") << 10; + QTest::newRow("Missing value in object") << QByteArray("{ \"value\": , } ") << 12; + QTest::newRow("Missing value in array") << QByteArray("[ \"value\" , , ] ") << 13; + QTest::newRow("Leading comma in object") << QByteArray("{ , \"value\": false}") << 3; + QTest::newRow("Leading comma in array") << QByteArray("[ , false]") << 3; + QTest::newRow("Stray ,") << QByteArray(" , ") << 3; + QTest::newRow("Stray [") << QByteArray(" [ ") << 5; + QTest::newRow("Stray }") << QByteArray(" } ") << 3; +} + +void tst_QtJson::parseErrorOffset() +{ + QFETCH(QByteArray, json); + QFETCH(int, errorOffset); + + QJsonParseError error; + QJsonDocument::fromJson(json, &error); + + QVERIFY(error.error != QJsonParseError::NoError); + QCOMPARE(error.offset, errorOffset); +} + +void tst_QtJson::implicitValueType() +{ + QJsonObject rootObject{ + {"object", QJsonObject{{"value", 42}}}, + {"array", QJsonArray{665, 666, 667}} + }; + + QJsonValue objectValue = rootObject["object"]; + QCOMPARE(objectValue["value"].toInt(), 42); + QCOMPARE(objectValue["missingValue"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectValue[123], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectValue["missingValue"].toInt(123), 123); + + QJsonValue arrayValue = rootObject["array"]; + QCOMPARE(arrayValue[1].toInt(), 666); + QCOMPARE(arrayValue[-1], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayValue["asObject"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayValue[-1].toInt(123), 123); + + const QJsonObject constObject = rootObject; + QCOMPARE(constObject["object"]["value"].toInt(), 42); + QCOMPARE(constObject["array"][1].toInt(), 666); + + QJsonValue objectAsValue(rootObject); + QCOMPARE(objectAsValue["object"]["value"].toInt(), 42); + QCOMPARE(objectAsValue["array"][1].toInt(), 666); +} + +void tst_QtJson::implicitDocumentType() +{ + QJsonDocument emptyDocument; + QCOMPARE(emptyDocument["asObject"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(emptyDocument[123], QJsonValue(QJsonValue::Undefined)); + + QJsonDocument objectDocument(QJsonObject{{"value", 42}}); + QCOMPARE(objectDocument["value"].toInt(), 42); + QCOMPARE(objectDocument["missingValue"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectDocument[123], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(objectDocument["missingValue"].toInt(123), 123); + + QJsonDocument arrayDocument(QJsonArray{665, 666, 667}); + QCOMPARE(arrayDocument[1].toInt(), 666); + QCOMPARE(arrayDocument[-1], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayDocument["asObject"], QJsonValue(QJsonValue::Undefined)); + QCOMPARE(arrayDocument[-1].toInt(123), 123); +} + +QTEST_MAIN(tst_QtJson) +#include "tst_qtjson.moc" |