// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include #include #include #define INVALID_UNICODE "\xCE\xBA\xE1" #define UNICODE_NON_CHARACTER "\xEF\xBF\xBF" #define UNICODE_DJE "\320\202" // Character from the Serbian Cyrillic alphabet using namespace Json; Q_DECLARE_METATYPE(Json::JsonArray) Q_DECLARE_METATYPE(Json::JsonObject) bool contains(const JsonObject::Keys &keys, const std::string &key) { return std::find(keys.begin(), keys.end(), key) != keys.end(); } class tst_Json: public QObject { Q_OBJECT public: tst_Json(QObject *parent = 0); private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testValueSimple(); void testNumbers(); void testNumbers_2(); void testNumbers_3(); void testObjectSimple(); void testObjectSmallKeys(); void testArraySimple(); void testValueObject(); void testValueArray(); void testObjectNested(); void testArrayNested(); void testArrayNestedEmpty(); 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 toJson(); void toJsonSillyNumericValues(); void toJsonLargeNumericValues(); void fromJson(); void fromJsonErrors(); void fromBinary(); void toAndFromBinary_data(); void toAndFromBinary(); void parseNumbers(); void parseStrings(); void parseDuplicateKeys(); void testParser(); void compactArray(); void compactObject(); void validation(); void assignToDocument(); void testDuplicateKeys(); void testCompaction(); 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(); private: QString testDataDir; }; tst_Json::tst_Json(QObject *parent) : QObject(parent) { } void tst_Json::initTestCase() { testDataDir = QFileInfo(QFINDTESTDATA("test.json")).absolutePath(); if (testDataDir.isEmpty()) testDataDir = QCoreApplication::applicationDirPath(); } void tst_Json::cleanupTestCase() { } void tst_Json::init() { } void tst_Json::cleanup() { } void tst_Json::testValueSimple() { JsonObject object; object.insert("number", 999.); JsonArray array; for (int i = 0; i < 10; ++i) array.append((double)i); JsonValue value(true); QCOMPARE(value.type(), JsonValue::Bool); QCOMPARE(value.toDouble(), 0.); QCOMPARE(value.toString(), std::string()); QCOMPARE(value.toBool(), true); QCOMPARE(value.toObject(), JsonObject()); QCOMPARE(value.toArray(), JsonArray()); QCOMPARE(value.toDouble(99.), 99.); QCOMPARE(value.toString("test"), std::string("test")); QCOMPARE(value.toObject(object), object); QCOMPARE(value.toArray(array), array); value = 999.; QCOMPARE(value.type(), JsonValue::Double); QCOMPARE(value.toDouble(), 999.); QCOMPARE(value.toString(), std::string()); QCOMPARE(value.toBool(), false); QCOMPARE(value.toBool(true), true); QCOMPARE(value.toObject(), JsonObject()); QCOMPARE(value.toArray(), JsonArray()); value = "test"; QCOMPARE(value.toDouble(), 0.); QCOMPARE(value.toString(), std::string("test")); QCOMPARE(value.toBool(), false); QCOMPARE(value.toObject(), JsonObject()); QCOMPARE(value.toArray(), JsonArray()); } void tst_Json::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); JsonArray array; for (int i = 0; i < n; ++i) array.append((double)numbers[i]); std::string serialized = JsonDocument(array).toJson(); JsonDocument json = JsonDocument::fromJson(serialized); JsonArray array2 = json.array(); QCOMPARE(array.size(), array2.size()); for (int i = 0; i < array.size(); ++i) { QCOMPARE(array.at(i).type(), JsonValue::Double); QCOMPARE(array.at(i).toDouble(), (double)numbers[i]); QCOMPARE(array2.at(i).type(), JsonValue::Double); QCOMPARE(array2.at(i).toDouble(), (double)numbers[i]); } } { int64_t 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(int64_t); JsonArray array; for (int i = 0; i < n; ++i) array.append((double)numbers[i]); std::string serialized = JsonDocument(array).toJson(); JsonDocument json = JsonDocument::fromJson(serialized); JsonArray array2 = json.array(); QCOMPARE(array.size(), array2.size()); for (int i = 0; i < array.size(); ++i) { QCOMPARE(array.at(i).type(), JsonValue::Double); QCOMPARE(array.at(i).toDouble(), (double)numbers[i]); QCOMPARE(array2.at(i).type(), JsonValue::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); JsonArray array; for (int i = 0; i < n; ++i) array.append(numbers[i]); std::string serialized = JsonDocument(array).toJson(); JsonDocument json = JsonDocument::fromJson(serialized); JsonArray array2 = json.array(); QCOMPARE(array.size(), array2.size()); for (int i = 0; i < array.size(); ++i) { QCOMPARE(array.at(i).type(), JsonValue::Double); QCOMPARE(array.at(i).toDouble(), numbers[i]); QCOMPARE(array2.at(i).type(), JsonValue::Double); QCOMPARE(array2.at(i).toDouble(), numbers[i]); } } } void tst_Json::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]; JsonObject jObject; for (int power = 0; power <= 1075; power++) { floatValues[power] = value; jObject.insert(std::to_string(power), JsonValue(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; } JsonDocument jDocument1(jObject); std::string ba(jDocument1.toJson()); JsonDocument jDocument2(JsonDocument::fromJson(ba)); for (int power = 0; power <= 1075; power++) { floatValues_1[power] = jDocument2.object().value(std::to_string(power)).toDouble(); #ifdef Q_OS_QNX if (power >= 970) QEXPECT_FAIL("", "See QTBUG-37066", Abort); #endif QVERIFY2(floatValues[power] == floatValues_1[power], QString::fromLatin1("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::fromLatin1("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::fromLatin1("2**- %1 should not be 0").arg(index).toLatin1()); QVERIFY2(floatValues_1[index - 1] == (floatValues_1[index] * 2), QString::fromLatin1("Value should be double adjacent value at index %1").arg(index).toLatin1()); } } void tst_Json::testNumbers_3() { // test case from QTBUG-31926 double d1 = 1.123451234512345; double d2 = 1.123451234512346; JsonObject jObject; jObject.insert("d1", JsonValue(d1)); jObject.insert("d2", JsonValue(d2)); JsonDocument jDocument1(jObject); std::string ba(jDocument1.toJson()); JsonDocument jDocument2(JsonDocument::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_Json::testObjectSimple() { JsonObject object; object.insert("number", 999.); QCOMPARE(object.value("number").type(), JsonValue::Double); QCOMPARE(object.value("number").toDouble(), 999.); object.insert("string", std::string("test")); QCOMPARE(object.value("string").type(), JsonValue::String); QCOMPARE(object.value("string").toString(), std::string("test")); object.insert("boolean", true); QCOMPARE(object.value("boolean").toBool(), true); JsonObject::Keys keys = object.keys(); QVERIFY2(contains(keys, "number"), "key number not found"); QVERIFY2(contains(keys, "string"), "key string not found"); QVERIFY2(contains(keys, "boolean"), "key boolean not found"); // if we put a JsonValue into the JsonObject and retrieve // it, it should be identical. JsonValue value("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"); JsonValue taken = object.take("value"); QCOMPARE(taken, value); QVERIFY2(!object.contains("value"), "key value should have been removed"); std::string before = object.value("string").toString(); object.insert("string", std::string("foo")); QVERIFY2(object.value("string").toString() != before, "value should have been updated"); size = object.size(); JsonObject subobject; subobject.insert("number", 42); subobject.insert("string", "foobar"); object.insert("subobject", subobject); QCOMPARE(object.size(), size+1); JsonValue subvalue = object.take("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("string", "bar"); QCOMPARE(object.size(), size); QCOMPARE(subvalue.toObject(), subobject); } void tst_Json::testObjectSmallKeys() { JsonObject data1; data1.insert("1", 123.); QVERIFY(data1.contains("1")); QCOMPARE(data1.value("1").toDouble(), (double)123); data1.insert("12", 133.); QCOMPARE(data1.value("12").toDouble(), (double)133); QVERIFY(data1.contains("12")); data1.insert("123", 323.); QCOMPARE(data1.value("12").toDouble(), (double)133); QVERIFY(data1.contains("123")); QCOMPARE(data1.value("123").type(), JsonValue::Double); QCOMPARE(data1.value("123").toDouble(), (double)323); } void tst_Json::testArraySimple() { JsonArray array; array.append(999.); array.append(std::string("test")); array.append(true); JsonValue val = array.at(0); QCOMPARE(array.at(0).toDouble(), 999.); QCOMPARE(array.at(1).toString(), std::string("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. JsonValue value("foo"); array.append(value); QCOMPARE(array.at(3), value); int size = array.size(); array.removeAt(2); --size; QCOMPARE(array.size(), size); JsonValue taken = array.takeAt(0); --size; QCOMPARE(taken.toDouble(), 999.); QCOMPARE(array.size(), size); // check whether null values work array.append(JsonValue()); ++size; QCOMPARE(array.size(), size); QCOMPARE(array.last().type(), JsonValue::Null); QCOMPARE(array.last(), JsonValue()); QCOMPARE(array.first().type(), JsonValue::String); QCOMPARE(array.first(), JsonValue("test")); array.prepend(false); QCOMPARE(array.first().type(), JsonValue::Bool); QCOMPARE(array.first(), JsonValue(false)); QCOMPARE(array.at(-1), JsonValue(JsonValue::Undefined)); QCOMPARE(array.at(array.size()), JsonValue(JsonValue::Undefined)); array.replace(0, -555.); QCOMPARE(array.first().type(), JsonValue::Double); QCOMPARE(array.first(), JsonValue(-555.)); QCOMPARE(array.at(1).type(), JsonValue::String); QCOMPARE(array.at(1), JsonValue("test")); } void tst_Json::testValueObject() { JsonObject object; object.insert("number", 999.); object.insert("string", "test"); object.insert("boolean", true); JsonValue 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", JsonValue("test")); QVERIFY2(value.toObject() != object, "object should have detached"); } void tst_Json::testValueArray() { JsonArray array; array.append(999.); array.append("test"); array.append(true); JsonValue 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("test"); QVERIFY2(value.toArray() != array, "array should have detached"); } void tst_Json::testObjectNested() { JsonObject 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). JsonObject 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 JsonArray 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 JsonObject 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_Json::testArrayNested() { JsonArray inner, outer; inner.append(999.); outer.append(inner); // if we don't modify the original JsonArray, value() // should return the same array (non-detached). JsonArray 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 JsonObject object; object.insert("boolean", true); outer.append(object); QCOMPARE(outer.last().toObject(), object); QCOMPARE(outer.last().toObject().value("boolean").toBool(), true); // two deep arrays JsonArray twoDeep; twoDeep.append(JsonValue("nested")); inner.append(twoDeep); outer.append(inner); QCOMPARE(outer.last().toArray().last().toArray(), twoDeep); QCOMPARE(outer.last().toArray().last().toArray().at(0).toString(), std::string("nested")); } void tst_Json::testArrayNestedEmpty() { JsonObject object; JsonArray inner; object.insert("inner", inner); JsonValue val = object.value("inner"); JsonArray 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()); JsonDocument(object).toBinaryData(); QCOMPARE(object.value("inner").toArray().size(), 0); } void tst_Json::testObjectNestedEmpty() { JsonObject object; JsonObject inner; JsonObject inner2; object.insert("inner", inner); object.insert("inner2", inner2); JsonObject 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(), JsonValue::Object); JsonDocument(object).toBinaryData(); QVERIFY(object.value("inner").toObject().isEmpty()); QVERIFY(object.value("inner2").toObject().isEmpty()); JsonDocument doc = JsonDocument::fromBinaryData(JsonDocument(object).toBinaryData()); QVERIFY(!doc.isNull()); JsonObject reconstituted(doc.object()); QCOMPARE(reconstituted.value("inner").toObject().size(), 0); QCOMPARE(reconstituted.value("inner").type(), JsonValue::Object); QCOMPARE(reconstituted.value("inner2").type(), JsonValue::Object); } void tst_Json::testValueRef() { JsonArray 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(), JsonValue::Bool); QCOMPARE(array.at(1).toBool(), false); JsonObject object; object["key"] = true; QCOMPARE(object.size(), 1); object.insert("null", JsonValue()); QCOMPARE(object.value("null"), JsonValue()); object["null"] = 100.; QCOMPARE(object.value("null").type(), JsonValue::Double); JsonValue val = object["null"]; QCOMPARE(val.toDouble(), 100.); QCOMPARE(object.size(), 2); array[1] = array[2] = object["key"] = 42; QCOMPARE(array[1], array[2]); QCOMPARE(array[2], object["key"]); QCOMPARE(object.value("key"), JsonValue(42)); } void tst_Json::testObjectIteration() { JsonObject object; for (JsonObject::iterator it = object.begin(); it != object.end(); ++it) QVERIFY(false); const std::string property = "kkk"; object.insert(property, 11); object.take(property); for (JsonObject::iterator it = object.begin(); it != object.end(); ++it) QVERIFY(false); for (int i = 0; i < 10; ++i) object[std::to_string(i)] = (double)i; QCOMPARE(object.size(), 10); QCOMPARE(object.begin()->toDouble(), object.constBegin()->toDouble()); for (JsonObject::iterator it = object.begin(); it != object.end(); ++it) { JsonValue value = it.value(); QCOMPARE((double)atoi(it.key().data()), value.toDouble()); } { JsonObject object2 = object; QCOMPARE(object, object2); JsonValue val = *object2.begin(); object2.erase(object2.begin()); QCOMPARE(object.size(), 10); QCOMPARE(object2.size(), 9); for (JsonObject::const_iterator it = object2.constBegin(); it != object2.constEnd(); ++it) { JsonValue value = it.value(); QVERIFY(it.value() != val); QCOMPARE((double)atoi(it.key().data()), value.toDouble()); } } { JsonObject object2 = object; QCOMPARE(object, object2); JsonObject::iterator it = object2.find(std::to_string(5)); object2.erase(it); QCOMPARE(object.size(), 10); QCOMPARE(object2.size(), 9); } { JsonObject::iterator it = object.begin(); it += 5; QCOMPARE(JsonValue(it.value()).toDouble(), 5.); it -= 3; QCOMPARE(JsonValue(it.value()).toDouble(), 2.); JsonObject::iterator it2 = it + 5; QCOMPARE(JsonValue(it2.value()).toDouble(), 7.); it2 = it - 1; QCOMPARE(JsonValue(it2.value()).toDouble(), 1.); } { JsonObject::const_iterator it = object.constBegin(); it += 5; QCOMPARE(JsonValue(it.value()).toDouble(), 5.); it -= 3; QCOMPARE(JsonValue(it.value()).toDouble(), 2.); JsonObject::const_iterator it2 = it + 5; QCOMPARE(JsonValue(it2.value()).toDouble(), 7.); it2 = it - 1; QCOMPARE(JsonValue(it2.value()).toDouble(), 1.); } JsonObject::iterator it = object.begin(); while (!object.isEmpty()) it = object.erase(it); QCOMPARE(object.size() , 0); QCOMPARE(it, object.end()); } void tst_Json::testArrayIteration() { JsonArray array; for (int i = 0; i < 10; ++i) array.append(i); QCOMPARE(array.size(), 10); int i = 0; for (JsonArray::iterator it = array.begin(); it != array.end(); ++it, ++i) { JsonValue value = (*it); QCOMPARE((double)i, value.toDouble()); } QCOMPARE(array.begin()->toDouble(), array.constBegin()->toDouble()); { JsonArray array2 = array; QCOMPARE(array, array2); JsonValue val = *array2.begin(); array2.erase(array2.begin()); QCOMPARE(array.size(), 10); QCOMPARE(array2.size(), 9); i = 1; for (JsonArray::const_iterator it = array2.constBegin(); it != array2.constEnd(); ++it, ++i) { JsonValue value = (*it); QCOMPARE((double)i, value.toDouble()); } } { JsonArray::iterator it = array.begin(); it += 5; QCOMPARE(JsonValue((*it)).toDouble(), 5.); it -= 3; QCOMPARE(JsonValue((*it)).toDouble(), 2.); JsonArray::iterator it2 = it + 5; QCOMPARE(JsonValue(*it2).toDouble(), 7.); it2 = it - 1; QCOMPARE(JsonValue(*it2).toDouble(), 1.); } { JsonArray::const_iterator it = array.constBegin(); it += 5; QCOMPARE(JsonValue((*it)).toDouble(), 5.); it -= 3; QCOMPARE(JsonValue((*it)).toDouble(), 2.); JsonArray::const_iterator it2 = it + 5; QCOMPARE(JsonValue(*it2).toDouble(), 7.); it2 = it - 1; QCOMPARE(JsonValue(*it2).toDouble(), 1.); } JsonArray::iterator it = array.begin(); while (!array.isEmpty()) it = array.erase(it); QCOMPARE(array.size() , 0); QCOMPARE(it, array.end()); } void tst_Json::testObjectFind() { JsonObject object; for (int i = 0; i < 10; ++i) object[std::to_string(i)] = i; QCOMPARE(object.size(), 10); JsonObject::iterator it = object.find("1"); QCOMPARE((*it).toDouble(), 1.); it = object.find("11"); QCOMPARE((*it).type(), JsonValue::Undefined); QCOMPARE(it, object.end()); JsonObject::const_iterator cit = object.constFind("1"); QCOMPARE((*cit).toDouble(), 1.); cit = object.constFind("11"); QCOMPARE((*it).type(), JsonValue::Undefined); QCOMPARE(it, object.end()); } void tst_Json::testDocument() { JsonDocument doc; QCOMPARE(doc.isEmpty(), true); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), false); JsonObject object; doc.setObject(object); QCOMPARE(doc.isEmpty(), false); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); object.insert("Key", "Value"); doc.setObject(object); QCOMPARE(doc.isEmpty(), false); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); QCOMPARE(doc.object(), object); QCOMPARE(doc.array(), JsonArray()); doc = JsonDocument(); QCOMPARE(doc.isEmpty(), true); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), false); JsonArray array; doc.setArray(array); QCOMPARE(doc.isEmpty(), false); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); array.append("Value"); doc.setArray(array); QCOMPARE(doc.isEmpty(), false); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); QCOMPARE(doc.array(), array); QCOMPARE(doc.object(), JsonObject()); JsonObject outer; outer.insert("outerKey", 22); JsonObject inner; inner.insert("innerKey", 42); outer.insert("innter", inner); JsonArray innerArray; innerArray.append(23); outer.insert("innterArray", innerArray); JsonDocument doc2(outer.value("innter").toObject()); QVERIFY(doc2.object().contains("innerKey")); QCOMPARE(doc2.object().value("innerKey"), JsonValue(42)); JsonDocument doc3; doc3.setObject(outer.value("innter").toObject()); QCOMPARE(doc3.isArray(), false); QCOMPARE(doc3.isObject(), true); QVERIFY(doc3.object().contains("innerKey")); QCOMPARE(doc3.object().value("innerKey"), JsonValue(42)); JsonDocument doc4(outer.value("innterArray").toArray()); QCOMPARE(doc4.isArray(), true); QCOMPARE(doc4.isObject(), false); QCOMPARE(doc4.array().size(), 1); QCOMPARE(doc4.array().at(0), JsonValue(23)); JsonDocument doc5; doc5.setArray(outer.value("innterArray").toArray()); QCOMPARE(doc5.isArray(), true); QCOMPARE(doc5.isObject(), false); QCOMPARE(doc5.array().size(), 1); QCOMPARE(doc5.array().at(0), JsonValue(23)); } void tst_Json::nullValues() { JsonArray array; array.append(JsonValue()); QCOMPARE(array.size(), 1); QCOMPARE(array.at(0), JsonValue()); JsonObject object; object.insert("key", JsonValue()); QCOMPARE(object.contains("key"), true); QCOMPARE(object.size(), 1); QCOMPARE(object.value("key"), JsonValue()); } void tst_Json::nullArrays() { JsonArray nullArray; JsonArray nonNull; nonNull.append("bar"); QCOMPARE(nullArray, JsonArray()); QVERIFY(nullArray != nonNull); QVERIFY(nonNull != nullArray); QCOMPARE(nullArray.size(), 0); QCOMPARE(nullArray.takeAt(0), JsonValue(JsonValue::Undefined)); QCOMPARE(nullArray.first(), JsonValue(JsonValue::Undefined)); QCOMPARE(nullArray.last(), JsonValue(JsonValue::Undefined)); nullArray.removeAt(0); nullArray.removeAt(-1); nullArray.append("bar"); nullArray.removeAt(0); QCOMPARE(nullArray.size(), 0); QCOMPARE(nullArray.takeAt(0), JsonValue(JsonValue::Undefined)); QCOMPARE(nullArray.first(), JsonValue(JsonValue::Undefined)); QCOMPARE(nullArray.last(), JsonValue(JsonValue::Undefined)); nullArray.removeAt(0); nullArray.removeAt(-1); } void tst_Json::nullObject() { JsonObject nullObject; JsonObject nonNull; nonNull.insert("foo", "bar"); QCOMPARE(nullObject, JsonObject()); QVERIFY(nullObject != nonNull); QVERIFY(nonNull != nullObject); QCOMPARE(nullObject.size(), 0); QCOMPARE(nullObject.keys(), JsonObject::Keys()); nullObject.remove("foo"); QCOMPARE(nullObject, JsonObject()); QCOMPARE(nullObject.take("foo"), JsonValue(JsonValue::Undefined)); QCOMPARE(nullObject.contains("foo"), false); nullObject.insert("foo", "bar"); nullObject.remove("foo"); QCOMPARE(nullObject.size(), 0); QCOMPARE(nullObject.keys(), JsonObject::Keys()); nullObject.remove("foo"); QCOMPARE(nullObject, JsonObject()); QCOMPARE(nullObject.take("foo"), JsonValue(JsonValue::Undefined)); QCOMPARE(nullObject.contains("foo"), false); } void tst_Json::constNullObject() { const JsonObject nullObject; JsonObject nonNull; nonNull.insert("foo", "bar"); QCOMPARE(nullObject, JsonObject()); QVERIFY(nullObject != nonNull); QVERIFY(nonNull != nullObject); QCOMPARE(nullObject.size(), 0); QCOMPARE(nullObject.keys(), JsonObject::Keys()); QCOMPARE(nullObject, JsonObject()); QCOMPARE(nullObject.contains("foo"), false); QCOMPARE(nullObject["foo"], JsonValue(JsonValue::Undefined)); } void tst_Json::keySorting() { const char *json = "{ \"B\": true, \"A\": false }"; JsonDocument doc = JsonDocument::fromJson(json); QCOMPARE(doc.isObject(), true); JsonObject o = doc.object(); QCOMPARE(o.size(), 2); JsonObject::const_iterator it = o.constBegin(); QCOMPARE(it.key(), std::string("A")); ++it; QCOMPARE(it.key(), std::string("B")); JsonObject::Keys keys; keys.push_back("A"); keys.push_back("B"); QCOMPARE(o.keys(), keys); } void tst_Json::undefinedValues() { JsonObject object; object.insert("Key", JsonValue(JsonValue::Undefined)); QCOMPARE(object.size(), 0); object.insert("Key", "Value"); QCOMPARE(object.size(), 1); QCOMPARE(object.value("Key").type(), JsonValue::String); QCOMPARE(object.value("foo").type(), JsonValue::Undefined); object.insert("Key", JsonValue(JsonValue::Undefined)); QCOMPARE(object.size(), 0); QCOMPARE(object.value("Key").type(), JsonValue::Undefined); JsonArray array; array.append(JsonValue(JsonValue::Undefined)); QCOMPARE(array.size(), 1); QCOMPARE(array.at(0).type(), JsonValue::Null); QCOMPARE(array.at(1).type(), JsonValue::Undefined); QCOMPARE(array.at(-1).type(), JsonValue::Undefined); } void tst_Json::toJson() { // Test JsonDocument::Indented format { JsonObject object; object.insert("\\Key\n", "Value"); object.insert("null", JsonValue()); JsonArray array; array.append(true); array.append(999.); array.append("string"); array.append(JsonValue()); array.append("\\\a\n\r\b\tabcABC\""); object.insert("Array", array); std::string json = JsonDocument(object).toJson(); std::string 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); JsonDocument 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 JsonDocument::Compact format { JsonObject object; object.insert("\\Key\n", "Value"); object.insert("null", JsonValue()); JsonArray array; array.append(true); array.append(999.); array.append("string"); array.append(JsonValue()); array.append("\\\a\n\r\b\tabcABC\""); object.insert("Array", array); std::string json = JsonDocument(object).toJson(JsonDocument::Compact); std::string expected = "{\"Array\":[true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"],\"\\\\Key\\n\":\"Value\",\"null\":null}"; QCOMPARE(json, expected); JsonDocument doc; doc.setObject(object); json = doc.toJson(JsonDocument::Compact); QCOMPARE(json, expected); doc.setArray(array); json = doc.toJson(JsonDocument::Compact); expected = "[true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"]"; QCOMPARE(json, expected); } } void tst_Json::toJsonSillyNumericValues() { JsonObject object; JsonArray array; array.append(JsonValue(std::numeric_limits::infinity())); // encode to: null array.append(JsonValue(-std::numeric_limits::infinity())); // encode to: null array.append(JsonValue(std::numeric_limits::quiet_NaN())); // encode to: null object.insert("Array", array); std::string json = JsonDocument(object).toJson(); std::string expected = "{\n" " \"Array\": [\n" " null,\n" " null,\n" " null\n" " ]\n" "}\n"; QCOMPARE(json, expected); JsonDocument doc; doc.setObject(object); json = doc.toJson(); QCOMPARE(json, expected); } void tst_Json::toJsonLargeNumericValues() { JsonObject object; JsonArray array; array.append(JsonValue(1.234567)); // actual precision bug in Qt 5.0.0 array.append(JsonValue(1.7976931348623157e+308)); // JS Number.MAX_VALUE array.append(JsonValue(5e-324)); // JS Number.MIN_VALUE array.append(JsonValue(std::numeric_limits::min())); array.append(JsonValue(std::numeric_limits::max())); array.append(JsonValue(std::numeric_limits::epsilon())); array.append(JsonValue(std::numeric_limits::denorm_min())); array.append(JsonValue(0.0)); array.append(JsonValue(-std::numeric_limits::min())); array.append(JsonValue(-std::numeric_limits::max())); array.append(JsonValue(-std::numeric_limits::epsilon())); array.append(JsonValue(-std::numeric_limits::denorm_min())); array.append(JsonValue(-0.0)); array.append(JsonValue(int64_t(9007199254740992LL))); // JS Number max integer array.append(JsonValue(int64_t(-9007199254740992LL))); // JS Number min integer object.insert("Array", array); std::string json = JsonDocument(object).toJson(); std::string expected = "{\n" " \"Array\": [\n" " 1.234567,\n" " 1.7976931348623157e+308,\n" // ((4.9406564584124654e-324 == 5e-324) == true) // I can only think JavaScript has a special formatter to // emit this value for this IEEE754 bit pattern. " 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" " 0,\n" " 9007199254740992,\n" " -9007199254740992\n" " ]\n" "}\n"; QCOMPARE(json, expected); JsonDocument doc; doc.setObject(object); json = doc.toJson(); QCOMPARE(json, expected); } void tst_Json::fromJson() { { std::string json = "[\n true\n]\n"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 1); QCOMPARE(array.at(0).type(), JsonValue::Bool); QCOMPARE(array.at(0).toBool(), true); QCOMPARE(doc.toJson(), json); } { //regression test: test if unicode_control_characters are correctly decoded std::string json = "[\n \"" UNICODE_NON_CHARACTER "\"\n]\n"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 1); QCOMPARE(array.at(0).type(), JsonValue::String); QCOMPARE(array.at(0).toString(), std::string(UNICODE_NON_CHARACTER)); QCOMPARE(doc.toJson(), json); } { std::string json = "[]"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 0); } { std::string json = "{}"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); JsonObject object = doc.object(); QCOMPARE(object.size(), 0); } { std::string json = "{\n \"Key\": true\n}\n"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); JsonObject object = doc.object(); QCOMPARE(object.size(), 1); QCOMPARE(object.value("Key"), JsonValue(true)); QCOMPARE(doc.toJson(), json); } { std::string json = "[ null, true, false, \"Foo\", 1, [], {} ]"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 7); QCOMPARE(array.at(0).type(), JsonValue::Null); QCOMPARE(array.at(1).type(), JsonValue::Bool); QCOMPARE(array.at(1).toBool(), true); QCOMPARE(array.at(2).type(), JsonValue::Bool); QCOMPARE(array.at(2).toBool(), false); QCOMPARE(array.at(3).type(), JsonValue::String); QCOMPARE(array.at(3).toString(), std::string("Foo")); QCOMPARE(array.at(4).type(), JsonValue::Double); QCOMPARE(array.at(4).toDouble(), 1.); QCOMPARE(array.at(5).type(), JsonValue::Array); QCOMPARE(array.at(5).toArray().size(), 0); QCOMPARE(array.at(6).type(), JsonValue::Object); QCOMPARE(array.at(6).toObject().size(), 0); } { std::string json = "{ \"0\": null, \"1\": true, \"2\": false, \"3\": \"Foo\", \"4\": 1, \"5\": [], \"6\": {} }"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); JsonObject object = doc.object(); QCOMPARE(object.size(), 7); QCOMPARE(object.value("0").type(), JsonValue::Null); QCOMPARE(object.value("1").type(), JsonValue::Bool); QCOMPARE(object.value("1").toBool(), true); QCOMPARE(object.value("2").type(), JsonValue::Bool); QCOMPARE(object.value("2").toBool(), false); QCOMPARE(object.value("3").type(), JsonValue::String); QCOMPARE(object.value("3").toString(), std::string("Foo")); QCOMPARE(object.value("4").type(), JsonValue::Double); QCOMPARE(object.value("4").toDouble(), 1.); QCOMPARE(object.value("5").type(), JsonValue::Array); QCOMPARE(object.value("5").toArray().size(), 0); QCOMPARE(object.value("6").type(), JsonValue::Object); QCOMPARE(object.value("6").toObject().size(), 0); } { std::string compactJson = "{\"Array\": [true,999,\"string\",null,\"\\\\\\u0007\\n\\r\\b\\tabcABC\\\"\"],\"\\\\Key\\n\": \"Value\",\"null\": null}"; JsonDocument doc = JsonDocument::fromJson(compactJson); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); JsonObject object = doc.object(); QCOMPARE(object.size(), 3); QCOMPARE(object.value("\\Key\n").isString(), true); QCOMPARE(object.value("\\Key\n").toString(), std::string("Value")); QCOMPARE(object.value("null").isNull(), true); QCOMPARE(object.value("Array").isArray(), true); JsonArray 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(), std::string("string")); QCOMPARE(array.at(3).isNull(), true); QCOMPARE(array.at(4).isString(), true); QCOMPARE(array.at(4).toString(), std::string("\\\a\n\r\b\tabcABC\"")); } } void tst_Json::fromJsonErrors() { { JsonParseError error; std::string json = "{\n \n\n"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::UnterminatedObject); QCOMPARE(error.offset, 8); } { JsonParseError error; std::string json = "{\n \"key\" 10\n"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::MissingNameSeparator); QCOMPARE(error.offset, 13); } { JsonParseError error; std::string json = "[\n \n\n"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::UnterminatedArray); QCOMPARE(error.offset, 8); } { JsonParseError error; std::string json = "[\n 1, true\n\n"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::UnterminatedArray); QCOMPARE(error.offset, 14); } { JsonParseError error; std::string json = "[\n 1 true\n\n"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::MissingValueSeparator); QCOMPARE(error.offset, 7); } { JsonParseError error; std::string json = "[\n nul"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalValue); QCOMPARE(error.offset, 7); } { JsonParseError error; std::string json = "[\n nulzz"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalValue); QCOMPARE(error.offset, 10); } { JsonParseError error; std::string json = "[\n tru"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalValue); QCOMPARE(error.offset, 7); } { JsonParseError error; std::string json = "[\n trud]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalValue); QCOMPARE(error.offset, 10); } { JsonParseError error; std::string json = "[\n fal"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalValue); QCOMPARE(error.offset, 7); } { JsonParseError error; std::string json = "[\n falsd]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalValue); QCOMPARE(error.offset, 11); } { JsonParseError error; std::string json = "[\n 11111"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::TerminationByNumber); QCOMPARE(error.offset, 11); } { JsonParseError error; std::string json = "[\n -1E10000]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalNumber); QCOMPARE(error.offset, 14); } { /* JsonParseError error; std::string json = "[\n -1e-10000]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalNumber); QCOMPARE(error.offset, 15); */ } { JsonParseError error; std::string json = "[\n \"\\u12\"]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalEscapeSequence); QCOMPARE(error.offset, 11); } { // This is not caught by the new parser as we don't parse // UTF-8 anymore, but pass it as opaque blob. // JsonParseError error; // std::string json = "[\n \"foo" INVALID_UNICODE "bar\"]"; // JsonDocument doc = JsonDocument::fromJson(json, &error); // QVERIFY(doc.isEmpty()); // QCOMPARE(error.error, JsonParseError::IllegalUTF8String); // QCOMPARE(error.offset, 12); } { JsonParseError error; std::string json = "[\n \""; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::UnterminatedString); QCOMPARE(error.offset, 8); } { JsonParseError error; std::string json = "[\n \"c" UNICODE_DJE "a\\u12\"]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::IllegalEscapeSequence); QCOMPARE(error.offset, 15); } { // This is not caught by the new parser as we don't parse // UTF-8 anymore, but pass it as opaque blob. // JsonParseError error; // std::string json = "[\n \"c" UNICODE_DJE "a" INVALID_UNICODE "bar\"]"; // JsonDocument doc = JsonDocument::fromJson(json, &error); // QVERIFY(doc.isEmpty()); // QCOMPARE(error.error, JsonParseError::IllegalUTF8String); // QCOMPARE(error.offset, 13); } { JsonParseError error; std::string json = "[\n \"c" UNICODE_DJE "a ]"; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isEmpty()); QCOMPARE(error.error, JsonParseError::UnterminatedString); QCOMPARE(error.offset, 14); } } void tst_Json::fromBinary() { QFile file(testDataDir + QLatin1String("/test.json")); file.open(QFile::ReadOnly); std::string testJson = file.readAll().data(); JsonDocument doc = JsonDocument::fromJson(testJson); JsonDocument outdoc = JsonDocument::fromBinaryData(doc.toBinaryData()); QVERIFY(!outdoc.isNull()); QCOMPARE(doc, outdoc); // // Can be used to re-create test.bjson: // QFile b1file(testDataDir + QLatin1String("/test.bjson.x")); // b1file.open(QFile::WriteOnly); // std::string d = doc.toBinaryData(); // b1file.write(d.data(), d.size()); // b1file.close(); QFile bfile(testDataDir + QLatin1String("/test.bjson")); bfile.open(QFile::ReadOnly); std::string binary = bfile.readAll().toStdString(); JsonDocument bdoc = JsonDocument::fromBinaryData(binary); QVERIFY(!bdoc.isNull()); QCOMPARE(doc, bdoc); } void tst_Json::toAndFromBinary_data() { QTest::addColumn("filename"); QTest::newRow("test.json") << QString(testDataDir + QLatin1String("/test.json")); QTest::newRow("test2.json") << QString(testDataDir + QLatin1String("/test2.json")); } void tst_Json::toAndFromBinary() { QFETCH(QString, filename); QFile file(filename); QVERIFY(file.open(QFile::ReadOnly)); std::string data = file.readAll().data(); JsonDocument doc = JsonDocument::fromJson(data); QVERIFY(!doc.isNull()); JsonDocument outdoc = JsonDocument::fromBinaryData(doc.toBinaryData()); QVERIFY(!outdoc.isNull()); QCOMPARE(doc, outdoc); } void tst_Json::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) { std::string json = "[ "; json += numbers[i].str; json += " ]"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 1); JsonValue val = array.at(0); QCOMPARE(val.type(), JsonValue::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) { std::string json = "[ "; json += numbers[i].str; json += " ]"; JsonDocument doc = JsonDocument::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); JsonArray array = doc.array(); QCOMPARE(array.size(), 1); JsonValue val = array.at(0); QCOMPARE(val.type(), JsonValue::Double); QCOMPARE(val.toDouble(), numbers[i].n); } } } void tst_Json::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) { std::string json = "[\n \""; json += strings[i]; json += "\"\n]\n"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 1); JsonValue val = array.at(0); QCOMPARE(val.type(), JsonValue::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) { std::string json = "[\n \""; json += pairs[i].in; json += "\"\n]\n"; std::string out = "[\n \""; out += pairs[i].out; out += "\"\n]\n"; JsonDocument doc = JsonDocument::fromJson(json); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), true); QCOMPARE(doc.isObject(), false); JsonArray array = doc.array(); QCOMPARE(array.size(), 1); JsonValue val = array.at(0); QCOMPARE(val.type(), JsonValue::String); QCOMPARE(doc.toJson(), out); } } void tst_Json::parseDuplicateKeys() { const char *json = "{ \"B\": true, \"A\": null, \"B\": false }"; JsonDocument doc = JsonDocument::fromJson(json); QCOMPARE(doc.isObject(), true); JsonObject o = doc.object(); QCOMPARE(o.size(), 2); JsonObject::const_iterator it = o.constBegin(); QCOMPARE(it.key(), std::string("A")); QCOMPARE(it.value(), JsonValue()); ++it; QCOMPARE(it.key(), std::string("B")); QCOMPARE(it.value(), JsonValue(false)); } void tst_Json::testParser() { QFile file(testDataDir + QLatin1String("/test.json")); file.open(QFile::ReadOnly); std::string testJson = file.readAll().data(); JsonDocument doc = JsonDocument::fromJson(testJson); QVERIFY(!doc.isEmpty()); } void tst_Json::compactArray() { JsonArray array; array.append("First Entry"); array.append("Second Entry"); array.append("Third Entry"); JsonDocument doc(array); auto s = doc.toBinaryData().size(); array.removeAt(1); doc.setArray(array); QVERIFY(s > doc.toBinaryData().size()); s = doc.toBinaryData().size(); QCOMPARE(doc.toJson(), std::string("[\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(), std::string("[\n" " \"Third Entry\"\n" "]\n")); array.removeAt(0); doc.setArray(array); QVERIFY(s > doc.toBinaryData().size()); s = doc.toBinaryData().size(); QCOMPARE(doc.toJson(), std::string("[\n" "]\n")); } void tst_Json::compactObject() { JsonObject object; object.insert("Key1", "First Entry"); object.insert("Key2", "Second Entry"); object.insert("Key3", "Third Entry"); JsonDocument doc(object); auto s = doc.toBinaryData().size(); object.remove("Key2"); doc.setObject(object); QVERIFY(s > doc.toBinaryData().size()); s = doc.toBinaryData().size(); QCOMPARE(doc.toJson(), std::string("{\n" " \"Key1\": \"First Entry\",\n" " \"Key3\": \"Third Entry\"\n" "}\n")); object.remove("Key1"); doc.setObject(object); QVERIFY(s > doc.toBinaryData().size()); s = doc.toBinaryData().size(); QCOMPARE(doc.toJson(), std::string("{\n" " \"Key3\": \"Third Entry\"\n" "}\n")); object.remove("Key3"); doc.setObject(object); QVERIFY(s > doc.toBinaryData().size()); s = doc.toBinaryData().size(); QCOMPARE(doc.toJson(), std::string("{\n" "}\n")); } void tst_Json::validation() { // this basically tests that we don't crash on corrupt data QFile file(testDataDir + QLatin1String("/test.json")); QVERIFY(file.open(QFile::ReadOnly)); std::string testJson = file.readAll().data(); QVERIFY(!testJson.empty()); JsonDocument doc = JsonDocument::fromJson(testJson); QVERIFY(!doc.isNull()); std::string binary = doc.toBinaryData(); // only test the first 1000 bytes. Testing the full file takes too long for (int i = 0; i < 1000; ++i) { std::string corrupted = binary; corrupted[i] = char(0xff); JsonDocument doc = JsonDocument::fromBinaryData(corrupted); if (doc.isNull()) continue; std::string json = doc.toJson(); } QFile file2(testDataDir + QLatin1String("/test3.json")); file2.open(QFile::ReadOnly); testJson = file2.readAll().data(); QVERIFY(!testJson.empty()); doc = JsonDocument::fromJson(testJson); QVERIFY(!doc.isNull()); binary = doc.toBinaryData(); for (size_t i = 0; i < binary.size(); ++i) { std::string corrupted = binary; corrupted[i] = char(0xff); JsonDocument doc = JsonDocument::fromBinaryData(corrupted); if (doc.isNull()) continue; std::string json = doc.toJson(); corrupted = binary; corrupted[i] = 0x00; doc = JsonDocument::fromBinaryData(corrupted); if (doc.isNull()) continue; json = doc.toJson(); } } void tst_Json::assignToDocument() { { const char *json = "{ \"inner\": { \"key\": true } }"; JsonDocument doc = JsonDocument::fromJson(json); JsonObject o = doc.object(); JsonValue inner = o.value("inner"); JsonDocument innerDoc(inner.toObject()); QVERIFY(innerDoc != doc); QCOMPARE(innerDoc.object(), inner.toObject()); } { const char *json = "[ [ true ] ]"; JsonDocument doc = JsonDocument::fromJson(json); JsonArray a = doc.array(); JsonValue inner = a.at(0); JsonDocument innerDoc(inner.toArray()); QVERIFY(innerDoc != doc); QCOMPARE(innerDoc.array(), inner.toArray()); } } void tst_Json::testDuplicateKeys() { JsonObject obj; obj.insert("foo", "bar"); obj.insert("foo", "zap"); QCOMPARE(obj.size(), 1); QCOMPARE(obj.value("foo").toString(), std::string("zap")); } void tst_Json::testCompaction() { // modify object enough times to trigger compactionCounter // and make sure the data is still valid JsonObject obj; for (int i = 0; i < 33; ++i) { obj.remove("foo"); obj.insert("foo", "bar"); } QCOMPARE(obj.size(), 1); QCOMPARE(obj.value("foo").toString(), std::string("bar")); JsonDocument doc = JsonDocument::fromBinaryData(JsonDocument(obj).toBinaryData()); QVERIFY(!doc.isNull()); QVERIFY(!doc.isEmpty()); QCOMPARE(doc.isArray(), false); QCOMPARE(doc.isObject(), true); QCOMPARE(doc.object(), obj); } void tst_Json::testCompactionError() { JsonObject schemaObject; schemaObject.insert("_Type", "_SchemaType"); schemaObject.insert("name", "Address"); schemaObject.insert("schema", JsonObject()); { JsonObject content(schemaObject); JsonDocument doc(content); QVERIFY(!doc.isNull()); QByteArray hash = QCryptographicHash::hash(doc.toBinaryData().data(), QCryptographicHash::Md5).toHex(); schemaObject.insert("_Version", hash.data()); } JsonObject schema; schema.insert("streetNumber", schema.value("number").toObject()); schemaObject.insert("schema", schema); { JsonObject content(schemaObject); content.remove("_Uuid"); content.remove("_Version"); JsonDocument doc(content); QVERIFY(!doc.isNull()); QByteArray hash = QCryptographicHash::hash(doc.toBinaryData().data(), QCryptographicHash::Md5).toHex(); schemaObject.insert("_Version", hash.data()); } } void tst_Json::parseUnicodeEscapes() { const std::string json = "[ \"A\\u00e4\\u00C4\" ]"; JsonDocument doc = JsonDocument::fromJson(json); JsonArray array = doc.array(); QString result = QLatin1String("A"); result += QChar(0xe4); result += QChar(0xc4); std::string expected = result.toUtf8().data(); QCOMPARE(array.first().toString(), expected); } void tst_Json::assignObjects() { const char *json = "[ { \"Key\": 1 }, { \"Key\": 2 } ]"; JsonDocument doc = JsonDocument::fromJson(json); JsonArray array = doc.array(); JsonObject object = array.at(0).toObject(); QCOMPARE(object.value("Key").toDouble(), 1.); object = array.at(1).toObject(); QCOMPARE(object.value("Key").toDouble(), 2.); } void tst_Json::assignArrays() { const char *json = "[ [ 1 ], [ 2 ] ]"; JsonDocument doc = JsonDocument::fromJson(json); JsonArray array = doc.array(); JsonArray inner = array.at(0).toArray() ; QCOMPARE(inner.at(0).toDouble(), 1.); inner= array.at(1).toArray(); QCOMPARE(inner.at(0).toDouble(), 2.); } void tst_Json::testTrailingComma() { const char *jsons[] = {"{ \"Key\": 1, }", "[ { \"Key\": 1 }, ]"}; for (unsigned i = 0; i < sizeof(jsons)/sizeof(jsons[0]); ++i) { JsonParseError error; JsonDocument doc = JsonDocument::fromJson(jsons[i], &error); QCOMPARE(error.error, JsonParseError::MissingObject); } } void tst_Json::testDetachBug() { JsonObject dynamic; JsonObject embedded; JsonObject local; embedded.insert("Key1", "Value1"); embedded.insert("Key2", "Value2"); dynamic.insert("Bogus", "bogusValue"); dynamic.insert("embedded", embedded); local = dynamic.value("embedded").toObject(); dynamic.remove("embedded"); QCOMPARE(local.keys().size(), size_t(2)); local.remove("Key1"); local.remove("Key2"); QCOMPARE(local.keys().size(), size_t(0)); local.insert("Key1", "anotherValue"); QCOMPARE(local.keys().size(), size_t(1)); } void tst_Json::valueEquals() { QCOMPARE(JsonValue(), JsonValue()); QVERIFY(JsonValue() != JsonValue(JsonValue::Undefined)); QVERIFY(JsonValue() != JsonValue(true)); QVERIFY(JsonValue() != JsonValue(1.)); QVERIFY(JsonValue() != JsonValue(JsonArray())); QVERIFY(JsonValue() != JsonValue(JsonObject())); QCOMPARE(JsonValue(true), JsonValue(true)); QVERIFY(JsonValue(true) != JsonValue(false)); QVERIFY(JsonValue(true) != JsonValue(JsonValue::Undefined)); QVERIFY(JsonValue(true) != JsonValue()); QVERIFY(JsonValue(true) != JsonValue(1.)); QVERIFY(JsonValue(true) != JsonValue(JsonArray())); QVERIFY(JsonValue(true) != JsonValue(JsonObject())); QCOMPARE(JsonValue(1), JsonValue(1)); QVERIFY(JsonValue(1) != JsonValue(2)); QCOMPARE(JsonValue(1), JsonValue(1.)); QVERIFY(JsonValue(1) != JsonValue(1.1)); QVERIFY(JsonValue(1) != JsonValue(JsonValue::Undefined)); QVERIFY(JsonValue(1) != JsonValue()); QVERIFY(JsonValue(1) != JsonValue(true)); QVERIFY(JsonValue(1) != JsonValue(JsonArray())); QVERIFY(JsonValue(1) != JsonValue(JsonObject())); QCOMPARE(JsonValue(1.), JsonValue(1.)); QVERIFY(JsonValue(1.) != JsonValue(2.)); QVERIFY(JsonValue(1.) != JsonValue(JsonValue::Undefined)); QVERIFY(JsonValue(1.) != JsonValue()); QVERIFY(JsonValue(1.) != JsonValue(true)); QVERIFY(JsonValue(1.) != JsonValue(JsonArray())); QVERIFY(JsonValue(1.) != JsonValue(JsonObject())); QCOMPARE(JsonValue(JsonArray()), JsonValue(JsonArray())); JsonArray nonEmptyArray; nonEmptyArray.append(true); QVERIFY(JsonValue(JsonArray()) != nonEmptyArray); QVERIFY(JsonValue(JsonArray()) != JsonValue(JsonValue::Undefined)); QVERIFY(JsonValue(JsonArray()) != JsonValue()); QVERIFY(JsonValue(JsonArray()) != JsonValue(true)); QVERIFY(JsonValue(JsonArray()) != JsonValue(1.)); QVERIFY(JsonValue(JsonArray()) != JsonValue(JsonObject())); QCOMPARE(JsonValue(JsonObject()), JsonValue(JsonObject())); JsonObject nonEmptyObject; nonEmptyObject.insert("Key", true); QVERIFY(JsonValue(JsonObject()) != nonEmptyObject); QVERIFY(JsonValue(JsonObject()) != JsonValue(JsonValue::Undefined)); QVERIFY(JsonValue(JsonObject()) != JsonValue()); QVERIFY(JsonValue(JsonObject()) != JsonValue(true)); QVERIFY(JsonValue(JsonObject()) != JsonValue(1.)); QVERIFY(JsonValue(JsonObject()) != JsonValue(JsonArray())); QCOMPARE(JsonValue("foo"), JsonValue("foo")); QCOMPARE(JsonValue("\x66\x6f\x6f"), JsonValue("foo")); QCOMPARE(JsonValue("\x62\x61\x72"), JsonValue("bar")); QCOMPARE(JsonValue(UNICODE_NON_CHARACTER), JsonValue(UNICODE_NON_CHARACTER)); QCOMPARE(JsonValue(UNICODE_DJE), JsonValue(UNICODE_DJE)); QCOMPARE(JsonValue("\xc3\xa9"), JsonValue("\xc3\xa9")); } void tst_Json::objectEquals_data() { QTest::addColumn("left"); QTest::addColumn("right"); QTest::addColumn("result"); QTest::newRow("two defaults") << JsonObject() << JsonObject() << true; JsonObject object1; object1.insert("property", 1); JsonObject object2; object2["property"] = 1; JsonObject 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 << JsonObject() << false; JsonObject empty; empty.insert("property", 1); empty.take("property"); QTest::newRow("default vs empty") << JsonObject() << empty << true; QTest::newRow("empty vs empty") << empty << empty << true; QTest::newRow("object vs empty") << object1 << empty << false; JsonObject referencedEmpty; referencedEmpty["undefined"]; QTest::newRow("referenced empty vs referenced empty") << referencedEmpty << referencedEmpty << true; QTest::newRow("referenced empty vs object") << referencedEmpty << object1 << false; JsonObject referencedObject1; referencedObject1.insert("property", 1); referencedObject1["undefined"]; JsonObject referencedObject2; referencedObject2.insert("property", 1); referencedObject2["aaaaaaaaa"]; // earlier then "property" referencedObject2["zzzzzzzzz"]; // after "property" QTest::newRow("referenced object vs default") << referencedObject1 << JsonObject() << false; QTest::newRow("referenced object vs referenced object") << referencedObject1 << referencedObject1 << true; QTest::newRow("referenced object vs object (different)") << referencedObject1 << object3 << false; } void tst_Json::objectEquals() { QFETCH(JsonObject, left); QFETCH(JsonObject, 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 JsonValue perspective QCOMPARE(JsonValue(left) == JsonValue(right), result); QCOMPARE(JsonValue(left) != JsonValue(right), !result); QCOMPARE(JsonValue(right) == JsonValue(left), result); QCOMPARE(JsonValue(right) != JsonValue(left), !result); } void tst_Json::arrayEquals_data() { QTest::addColumn("left"); QTest::addColumn("right"); QTest::addColumn("result"); QTest::newRow("two defaults") << JsonArray() << JsonArray() << true; JsonArray array1; array1.append(1); JsonArray array2; array2.append(2111); array2[0] = 1; JsonArray 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 << JsonArray() << false; JsonArray empty; empty.append(1); empty.takeAt(0); QTest::newRow("default vs empty") << JsonArray() << empty << true; QTest::newRow("empty vs default") << empty << JsonArray() << true; QTest::newRow("empty vs empty") << empty << empty << true; QTest::newRow("array vs empty") << array1 << empty << false; } void tst_Json::arrayEquals() { QFETCH(JsonArray, left); QFETCH(JsonArray, 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 JsonValue perspective QCOMPARE(JsonValue(left) == JsonValue(right), result); QCOMPARE(JsonValue(left) != JsonValue(right), !result); QCOMPARE(JsonValue(right) == JsonValue(left), result); QCOMPARE(JsonValue(right) != JsonValue(left), !result); } void tst_Json::bom() { QFile file(testDataDir + QLatin1String("/bom.json")); file.open(QFile::ReadOnly); std::string json = file.readAll().data(); // Import json document into a JsonDocument JsonParseError error; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(!doc.isNull()); QCOMPARE(error.error, JsonParseError::NoError); } void tst_Json::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 = "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]" "]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"; std::string json(array_data); JsonParseError error; JsonDocument doc = JsonDocument::fromJson(json, &error); QVERIFY(!doc.isNull()); QCOMPARE(error.error, JsonParseError::NoError); json = '[' + json + ']'; doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isNull()); QCOMPARE(error.error, JsonParseError::DeepNesting); json = std::string("true "); for (int i = 0; i < 1024; ++i) json = "{ \"Key\": " + json + " }"; doc = JsonDocument::fromJson(json, &error); QVERIFY(!doc.isNull()); QCOMPARE(error.error, JsonParseError::NoError); json = '[' + json + ']'; doc = JsonDocument::fromJson(json, &error); QVERIFY(doc.isNull()); QCOMPARE(error.error, JsonParseError::DeepNesting); } void tst_Json::longStrings() { #if 0 // 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 map; map["key"] = s; /* Create a JsonDocument from the QMap ... */ JsonDocument d1 = JsonDocument::fromVariant(QVariant(map)); /* ... and a std::string from the JsonDocument */ std::string a1 = d1.toJson(); /* Create a JsonDocument from the std::string ... */ JsonDocument d2 = JsonDocument::fromJson(a1); /* ... and a std::string from the JsonDocument */ std::string a2 = d2.toJson(); QCOMPARE(a1, a2); } s = QString(0xfff0, 'a'); for (int i = 0xfff0; i < 0x10010; i++) { s.append(QLatin1Char('c')); QMap map; map["key"] = s; /* Create a JsonDocument from the QMap ... */ JsonDocument d1 = JsonDocument::fromVariant(QVariant(map)); /* ... and a std::string from the JsonDocument */ std::string a1 = d1.toJson(); /* Create a JsonDocument from the std::string ... */ JsonDocument d2 = JsonDocument::fromJson(a1); /* ... and a std::string from the JsonDocument */ std::string a2 = d2.toJson(); QCOMPARE(a1, a2); } #endif } void tst_Json::testJsonValueRefDefault() { JsonObject empty; QCOMPARE(empty["n/a"].toString(), std::string()); QCOMPARE(empty["n/a"].toString("default"), std::string("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_Json::arrayInitializerList() { #ifndef Q_COMPILER_INITIALIZER_LISTS QSKIP("initializer_list is enabled only with c++11 support"); #else QVERIFY(JsonArray{}.isEmpty()); QCOMPARE(JsonArray{"one"}.count(), 1); QCOMPARE(JsonArray{1}.count(), 1); { JsonArray a{1.3, "hello", 0}; QCOMPARE(JsonValue(a[0]), JsonValue(1.3)); QCOMPARE(JsonValue(a[1]), JsonValue("hello")); QCOMPARE(JsonValue(a[2]), JsonValue(0)); QCOMPARE(a.count(), 3); } { JsonObject o; o["property"] = 1; JsonArray a1{o}; QCOMPARE(a1.count(), 1); QCOMPARE(a1[0].toObject(), o); JsonArray a2{o, 23}; QCOMPARE(a2.count(), 2); QCOMPARE(a2[0].toObject(), o); QCOMPARE(JsonValue(a2[1]), JsonValue(23)); JsonArray a3{a1, o, a2}; QCOMPARE(JsonValue(a3[0]), JsonValue(a1)); QCOMPARE(JsonValue(a3[1]), JsonValue(o)); QCOMPARE(JsonValue(a3[2]), JsonValue(a2)); JsonArray a4{1, JsonArray{1,2,3}, JsonArray{"hello", 2}, JsonObject{{"one", 1}}}; QCOMPARE(a4.count(), 4); QCOMPARE(JsonValue(a4[0]), JsonValue(1)); { JsonArray a41 = a4[1].toArray(); JsonArray a42 = a4[2].toArray(); JsonObject a43 = a4[3].toObject(); QCOMPARE(a41.count(), 3); QCOMPARE(a42.count(), 2); QCOMPARE(a43.count(), 1); QCOMPARE(JsonValue(a41[2]), JsonValue(3)); QCOMPARE(JsonValue(a42[1]), JsonValue(2)); QCOMPARE(JsonValue(a43["one"]), JsonValue(1)); } } #endif } void tst_Json::objectInitializerList() { #ifndef Q_COMPILER_INITIALIZER_LISTS QSKIP("initializer_list is enabled only with c++11 support"); #else QVERIFY(JsonObject{}.isEmpty()); { // one property JsonObject one {{"one", 1}}; QCOMPARE(one.count(), 1); QVERIFY(one.contains("one")); QCOMPARE(JsonValue(one["one"]), JsonValue(1)); } { // two properties JsonObject two { {"one", 1}, {"two", 2} }; QCOMPARE(two.count(), 2); QVERIFY(two.contains("one")); QVERIFY(two.contains("two")); QCOMPARE(JsonValue(two["one"]), JsonValue(1)); QCOMPARE(JsonValue(two["two"]), JsonValue(2)); } { // nested object JsonObject object{{"nested", JsonObject{{"innerProperty", 2}}}}; QCOMPARE(object.count(), 1); QVERIFY(object.contains("nested")); QVERIFY(object["nested"].isObject()); JsonObject nested = object["nested"].toObject(); QCOMPARE(JsonValue(nested["innerProperty"]), JsonValue(2)); } { // nested array JsonObject object{{"nested", JsonArray{"innerValue", 2.1, "bum cyk cyk"}}}; QCOMPARE(object.count(), 1); QVERIFY(object.contains("nested")); QVERIFY(object["nested"].isArray()); JsonArray nested = object["nested"].toArray(); QCOMPARE(nested.count(), 3); QCOMPARE(JsonValue(nested[0]), JsonValue("innerValue")); QCOMPARE(JsonValue(nested[1]), JsonValue(2.1)); } #endif } void tst_Json::unicodeKeys() { std::string json = "{" "\"x\\u2090_1\": \"hello_1\"," "\"y\\u2090_2\": \"hello_2\"," "\"T\\u2090_3\": \"hello_3\"," "\"xyz_4\": \"hello_4\"," "\"abc_5\": \"hello_5\"" "}"; JsonParseError error; JsonDocument doc = JsonDocument::fromJson(json, &error); QCOMPARE(error.error, JsonParseError::NoError); JsonObject o = doc.object(); QCOMPARE(o.keys().size(), size_t(5)); const JsonObject::Keys keys = o.keys(); for (const std::string &k : keys) { QByteArray key(k.data()); std::string suffix = key.mid(key.indexOf('_')).data(); QCOMPARE(o[key.data()].toString(), "hello" + suffix); } } void tst_Json::garbageAtEnd() { JsonParseError error; JsonDocument doc = JsonDocument::fromJson("{},", &error); QCOMPARE(error.error, JsonParseError::GarbageAtEnd); QCOMPARE(error.offset, 2); QVERIFY(doc.isEmpty()); doc = JsonDocument::fromJson("{} ", &error); QCOMPARE(error.error, JsonParseError::NoError); QVERIFY(!doc.isEmpty()); } void tst_Json::removeNonLatinKey() { const std::string nonLatinKeyName = "Атрибут100500"; JsonObject sourceObject; sourceObject.insert("code", 1); sourceObject.remove("code"); sourceObject.insert(nonLatinKeyName, 1); const std::string json = JsonDocument(sourceObject).toJson(); const JsonObject restoredObject = JsonDocument::fromJson(json).object(); QCOMPARE(sourceObject.keys(), restoredObject.keys()); QVERIFY(sourceObject.contains(nonLatinKeyName)); QVERIFY(restoredObject.contains(nonLatinKeyName)); } QTEST_GUILESS_MAIN(tst_Json) #include "tst_json.moc"