diff --git a/templates/lib/metatype.cpp b/templates/lib/metatype.cpp index 5cad5c8..d92d39e 100644 --- a/templates/lib/metatype.cpp +++ b/templates/lib/metatype.cpp @@ -25,6 +25,10 @@ #include "metaenumvariable_p.h" #include +#include +#include +#include +#include using namespace Cutelee; @@ -106,12 +110,94 @@ static QVariant doQobjectLookUp(const QObject *const object, return object->property(property.toUtf8().constData()); } +static QVariant doJsonArrayLookUp(const QJsonArray &list, + const QString &property) +{ + if (property == QLatin1String("count") || property == QLatin1String("size")) { + return list.size(); + } + + bool ok = false; + const int listIndex = property.toInt(&ok); + if (!ok || listIndex >= list.size()) { + return QVariant(); + } + + return list.at(listIndex).toVariant(); +} + +static QVariant doJsonObjectLookUp(const QJsonObject &obj, + const QString &property) +{ + if (property == QLatin1String("count") || property == QLatin1String("size")) { + return obj.size(); + } + + if (property == QLatin1String("items")) { + QVariantList list; + list.reserve(obj.size()); + for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) { + list.push_back(QVariantList{it.key(), it.value().toVariant()}); + } + return list; + } + + if (property == QLatin1String("keys")) { + return obj.keys(); + } + + if (property == QLatin1String("values")) { + QVariantList list; + list.reserve(obj.size()); + for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) { + list.push_back(it.value().toVariant()); + } + return list; + } + + return obj.value(property).toVariant(); +} + QVariant Cutelee::MetaType::lookup(const QVariant &object, - const QString &property) + const QString &property) { if (object.canConvert()) { return doQobjectLookUp(object.value(), property); } + if (object.userType() == QMetaType::QJsonDocument) { + const auto doc = object.toJsonDocument(); + if (doc.isObject()) { + return doJsonObjectLookUp(doc.object(), property); + } + if (doc.isArray()) { + return doJsonArrayLookUp(doc.array(), property); + } + return QVariant(); + } + if (object.userType() == QMetaType::QJsonValue) { + const auto val = object.toJsonValue(); + + switch (val.type()) { + case QJsonValue::Bool: + return val.toBool(); + case QJsonValue::Double: + return val.toDouble(); + case QJsonValue::String: + return val.toString(); + case QJsonValue::Array: + return doJsonArrayLookUp(val.toArray(), property); + case QJsonValue::Object: + return doJsonObjectLookUp(val.toObject(), property); + default: + return QVariant(); + } + } + if (object.userType() == QMetaType::QJsonArray) { + return doJsonArrayLookUp(object.toJsonArray(), property); + } + if (object.userType() == QMetaType::QJsonObject) { + return doJsonObjectLookUp(object.toJsonObject(), property); + } if (object.canConvert()) { auto iter = object.value(); if (property == QStringLiteral("size") diff --git a/templates/lib/variable.cpp b/templates/lib/variable.cpp index b721d24..4815f5e 100644 --- a/templates/lib/variable.cpp +++ b/templates/lib/variable.cpp @@ -29,6 +29,10 @@ #include #include +#include +#include +#include +#include using namespace Cutelee; @@ -206,6 +210,40 @@ QVariant Variable::resolve(Context *c) const } else { var = c->lookup(d->m_lookups.at(i++)); + if (var.userType() == QMetaType::QJsonDocument) { + const auto jsonDoc = var.toJsonDocument(); + if (jsonDoc.isArray()) { + var = jsonDoc.array().toVariantList(); + } else if (jsonDoc.isObject()) { + var = jsonDoc.object().toVariantHash(); + } else { + // JSON document is eather empty or null + return QVariant(); + } + } else if (var.userType() == QMetaType::QJsonValue) { + const auto jsonVal = var.toJsonValue(); + switch(jsonVal.type()) { + case QJsonValue::Bool: + var = jsonVal.toBool(); + break; + case QJsonValue::Double: + var = jsonVal.toDouble(); + break; + case QJsonValue::String: + var = jsonVal.toString(); + break; + case QJsonValue::Array: + case QJsonValue::Object: + var = jsonVal.toVariant(); + break; + default: + return QVariant(); + } + } else if (var.userType() == QMetaType::QJsonArray) { + var = var.toJsonArray().toVariantList(); + } else if (var.userType() == QMetaType::QJsonObject) { + var = var.toJsonObject().toVariantHash(); + } } while (i < d->m_lookups.size()) { var = MetaType::lookup(var, d->m_lookups.at(i++)); diff --git a/templates/tests/testgenerictypes.cpp b/templates/tests/testgenerictypes.cpp index 48e430d..8c925f9 100644 --- a/templates/tests/testgenerictypes.cpp +++ b/templates/tests/testgenerictypes.cpp @@ -31,6 +31,10 @@ #include #include #include +#include +#include +#include +#include #include #include "coverageobject.h" @@ -73,6 +77,8 @@ private Q_SLOTS: void testQGadget(); void testGadgetMetaType(); + void testJsonTypes(); + }; // class TestGenericTypes class Person @@ -927,5 +933,345 @@ void TestGenericTypes::propertyMacroTypes() } } +void TestGenericTypes::testJsonTypes() +{ + Cutelee::Engine engine; + engine.setPluginPaths({QStringLiteral(CUTELEE_PLUGIN_PATH)}); + + Cutelee::Context c; + + QJsonArray arr; + arr.push_back(QJsonObject({ + {QStringLiteral("name"), QStringLiteral("Joe")}, + {QStringLiteral("age"), 20} + })); + QJsonObject obj({ + {QStringLiteral("name"), QStringLiteral("Mike")}, + {QStringLiteral("age"), 22} + }); + arr.push_back(obj); + + c.insert(QStringLiteral("arr"), arr); + c.insert(QStringLiteral("obj"), obj); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ arr.count }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("2"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{{ arr.1.name }}({{ arr.1.age }})"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Mike(22)"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for person in arr %}{{ person.name }}({{ person.age }})\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for name,age in arr %}{{ name }}({{ age }})\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{{ obj.count }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("2"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{{ obj.name }}({{ obj.age }})"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Mike(22)"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for key in obj.keys %}{{ key }}\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + + QVERIFY(result == QStringLiteral("name\nage\n") || result == QStringLiteral("age\nname\n")); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for val in obj.values %}{{ val }}\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + + QVERIFY(result == QStringLiteral("Mike\n22\n") || result == QStringLiteral("22\nMike\n")); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for item in obj.items %}{{ item.0 }}:{{ item.1 }}\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + + QVERIFY(result == QStringLiteral("age:22\nname:Mike\n") || result == QStringLiteral("name:Mike\nage:22\n")); + } + + QJsonDocument arrDoc(arr); + c.insert(QStringLiteral("arrDoc"), arrDoc); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ arrDoc.count }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("2"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{{ arrDoc.1.name }}({{ arrDoc.1.age }})"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Mike(22)"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for person in arrDoc %}{{ person.name }}({{ person.age }})\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for name,age in arrDoc %}{{ name }}({{ age }})\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n"); + + QCOMPARE(result, expectedResult); + } + + QJsonDocument objDoc(obj); + c.insert(QStringLiteral("objDoc"), objDoc); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ objDoc.count }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("2"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{{ objDoc.name }}({{ objDoc.age }})"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Mike(22)"); + + QCOMPARE(result, expectedResult); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for key in objDoc.keys %}{{ key }}\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + + QVERIFY(result == QStringLiteral("name\nage\n") || result == QStringLiteral("age\nname\n")); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for val in objDoc.values %}{{ val }}\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + + QVERIFY(result == QStringLiteral("Mike\n22\n") || result == QStringLiteral("22\nMike\n")); + } + + { + auto t = engine.newTemplate( + QStringLiteral("{% for item in objDoc.items %}{{ item.0 }}:{{ item.1 }}\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + + QVERIFY(result == QStringLiteral("age:22\nname:Mike\n") || result == QStringLiteral("name:Mike\nage:22\n")); + } + + QJsonObject emptyObj; + c.insert(QStringLiteral("emptyObj"), emptyObj); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ emptyObj.name }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral(""); + + QCOMPARE(result, expectedResult); + } + + QJsonArray emptyArr; + c.insert(QStringLiteral("emptyArr"), emptyArr); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ emptyArr.1 }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral(""); + + QCOMPARE(result, expectedResult); + } + + QJsonDocument emptyDoc; + c.insert(QStringLiteral("emptyDoc"), emptyDoc); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ emptyDoc }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral(""); + + QCOMPARE(result, expectedResult); + } + + c.insert(QStringLiteral("valBool"), QJsonValue(true)); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ valBool }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("true"); + + QCOMPARE(result, expectedResult); + } + + c.insert(QStringLiteral("valDouble"), QJsonValue(15)); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ valDouble }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("15"); + + QCOMPARE(result, expectedResult); + } + + c.insert(QStringLiteral("valString"), QJsonValue(QStringLiteral("Sapere aude"))); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ valString }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Sapere aude"); + + QCOMPARE(result, expectedResult); + } + + c.insert(QStringLiteral("valArray"), QJsonValue(arr)); + + { + auto t = engine.newTemplate( + QStringLiteral("{% for person in valArray %}{{ person.name }}({{ person.age }})\n{% endfor %}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Joe(20)\nMike(22)\n"); + + QCOMPARE(result, expectedResult); + } + + c.insert(QStringLiteral("valObj"), QJsonValue(obj)); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ valObj.name }}({{ valObj.age }})"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral("Mike(22)"); + + QCOMPARE(result, expectedResult); + } + + c.insert(QStringLiteral("valNull"), QJsonValue()); + + { + auto t = engine.newTemplate( + QStringLiteral("{{ valNull }}"), + QStringLiteral("template")); + + auto result = t->render(&c); + auto expectedResult = QStringLiteral(""); + + QCOMPARE(result, expectedResult); + } +} + QTEST_MAIN(TestGenericTypes) #include "testgenerictypes.moc"