File indexing completed on 2024-05-12 04:42:43

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "json_p.h"
0008 #include "logging.h"
0009 
0010 #include <QColor>
0011 #include <QDateTime>
0012 #include <QDebug>
0013 #include <QLocale>
0014 #include <QMetaObject>
0015 #include <QMetaProperty>
0016 #include <QRectF>
0017 #include <QTimeZone>
0018 #include <QUrl>
0019 #include <QVariant>
0020 
0021 #include <cmath>
0022 
0023 using namespace KPublicTransport;
0024 
0025 QString Json::translatedValue(const QJsonObject &obj, const QString &key)
0026 {
0027     auto languageWithCountry = QLocale().name();
0028     auto it = obj.constFind(key + QLatin1Char('[') + languageWithCountry + QLatin1Char(']'));
0029     if (it != obj.constEnd()) {
0030         return it.value().toString();
0031     }
0032     const auto language = QStringView(languageWithCountry).mid(0, languageWithCountry.indexOf(QLatin1Char('_')));
0033     it = obj.constFind(key + QLatin1Char('[') + language + QLatin1Char(']'));
0034     if (it != obj.constEnd()) {
0035         return it.value().toString();
0036     }
0037     return obj.value(key).toString();
0038 }
0039 
0040 QStringList Json::toStringList(const QJsonValue &v)
0041 {
0042     const auto a = v.toArray();
0043     QStringList l;
0044     l.reserve(a.size());
0045     for (const auto &av : a) {
0046         l.push_back(av.toString());
0047     }
0048     return l;
0049 }
0050 
0051 static QJsonValue variantToJson(const QVariant &v)
0052 {
0053     switch (v.userType()) {
0054         case QMetaType::QString:
0055         {
0056             const auto s = v.toString();
0057             return s.isNull() ? QJsonValue() : v.toString();
0058         }
0059         case QMetaType::Double:
0060         case QMetaType::Float:
0061         {
0062             auto d = v.toDouble();
0063             if (std::isnan(d)) {
0064                 return QJsonValue::Null;
0065             }
0066             return d;
0067         }
0068         case QMetaType::Int:
0069             return v.toInt();
0070         case QMetaType::QDateTime:
0071         {
0072             const auto dt = v.toDateTime();
0073             if (!dt.isValid()) {
0074                 return {};
0075             }
0076             if (dt.timeSpec() == Qt::TimeZone) {
0077                 QJsonObject dtObj;
0078                 dtObj.insert(QLatin1String("value"), dt.toString(Qt::ISODate));
0079                 dtObj.insert(QLatin1String("timezone"), QString::fromUtf8(dt.timeZone().id()));
0080                 return dtObj;
0081             }
0082             return v.toDateTime().toString(Qt::ISODate);
0083         }
0084         case QMetaType::QUrl:
0085         {
0086             const auto url = v.toUrl();
0087             return url.isValid() ? url.toString() : QJsonValue();
0088         }
0089         case QMetaType::QRectF:
0090         {
0091             const auto r = v.toRectF();
0092             QJsonObject obj;
0093             obj.insert(QLatin1String("x1"), r.topLeft().x());
0094             obj.insert(QLatin1String("y1"), r.topLeft().y());
0095             obj.insert(QLatin1String("x2"), r.bottomRight().x());
0096             obj.insert(QLatin1String("y2"), r.bottomRight().y());
0097             return obj;
0098         }
0099         case QMetaType::QColor:
0100         {
0101             const auto c = v.value<QColor>();
0102             return c.isValid() ? v.value<QColor>().name() : QJsonValue();;
0103         }
0104         case QMetaType::Bool:
0105             return v.toBool();
0106     }
0107 
0108     if (QMetaType mt(v.userType()); mt.metaObject() && (mt.flags() & QMetaType::IsEnumeration)) {
0109         return v.toString();
0110     }
0111 
0112     if (v.canConvert<QVariantList>()) {
0113         const auto l = v.toList();
0114         if (l.isEmpty()) {
0115             return {};
0116         }
0117 
0118         QJsonArray a;
0119         std::transform(l.begin(), l.end(), std::back_inserter(a), variantToJson);
0120         return a;
0121     }
0122 
0123     return {};
0124 }
0125 
0126 QJsonObject Json::toJson(const QMetaObject *mo, const void *elem)
0127 {
0128     QJsonObject obj;
0129 
0130     for (int i = 0; i < mo->propertyCount(); ++i) {
0131         const auto prop = mo->property(i);
0132         if (!prop.isStored()) {
0133             continue;
0134         }
0135 
0136         if (prop.isFlagType()) { // flag has to come first, as prop.isEnumType() is also true for this
0137             const auto key = prop.readOnGadget(elem).toInt();
0138             const auto value = prop.enumerator().valueToKeys(key);
0139             obj.insert(QString::fromUtf8(prop.name()), QString::fromUtf8(value));
0140             continue;
0141         }
0142         if (prop.isEnumType()) { // enums defined in this QMO
0143             const auto key = prop.readOnGadget(elem).toInt();
0144             const auto value = prop.enumerator().valueToKey(key);
0145             obj.insert(QString::fromUtf8(prop.name()), QString::fromUtf8(value));
0146             continue;
0147         } else if (QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) { // external enums
0148             obj.insert(QString::fromUtf8(prop.name()), prop.readOnGadget(elem).toString());
0149             continue;
0150         }
0151 
0152         const auto v = variantToJson(prop.readOnGadget(elem));
0153         if (!v.isNull()) {
0154             obj.insert(QString::fromUtf8(prop.name()), v);
0155         }
0156     }
0157 
0158     return obj;
0159 }
0160 
0161 // cache timezones by IANA id, with Qt6 QTimeZone(QByteArray) is unreasonably slow
0162 // on Android, so that loading Itinerary's cached public transport data takes up to 20secs...
0163 // can and should be removed once this has been fixed in Qt
0164 static QTimeZone timeZone(const QByteArray &tzId)
0165 {
0166     static QHash<QByteArray, QTimeZone> s_tzCache;
0167     const auto it = s_tzCache.constFind(tzId);
0168     if (it != s_tzCache.constEnd()) {
0169         return it.value();
0170     }
0171     auto tz = QTimeZone(tzId);
0172     s_tzCache.insert(tzId, tz);
0173     return tz;
0174 }
0175 
0176 static QVariant variantFromJson(const QJsonValue &v, int mt)
0177 {
0178     switch (mt) {
0179         case QMetaType::QString:
0180             return v.toString();
0181         case QMetaType::Double:
0182         case QMetaType::Float:
0183             return v.toDouble();
0184         case QMetaType::Int:
0185             return v.toInt();
0186         case QMetaType::QDateTime:
0187         {
0188             if (v.isObject()) {
0189                 const auto dtObj = v.toObject();
0190                 auto dt = QDateTime::fromString(dtObj.value(QLatin1String("value")).toString(), Qt::ISODate);
0191                 dt.setTimeZone(timeZone(dtObj.value(QLatin1String("timezone")).toString().toUtf8()));
0192                 return dt;
0193             }
0194             return QDateTime::fromString(v.toString(), Qt::ISODate);
0195         }
0196         case QMetaType::QUrl:
0197             return QUrl(v.toString());
0198         case QMetaType::QStringList:
0199             return Json::toStringList(v);
0200         case QMetaType::QRectF:
0201         {
0202             const auto obj = v.toObject();
0203             QRectF r;
0204             r.setTopLeft(QPointF(obj.value(QLatin1String("x1")).toDouble(), obj.value(QLatin1String("y1")).toDouble()));
0205             r.setBottomRight(QPointF(obj.value(QLatin1String("x2")).toDouble(), obj.value(QLatin1String("y2")).toDouble()));
0206             return r;
0207         }
0208         case QMetaType::QColor:
0209             return QColor(v.toString());
0210     }
0211 
0212     return {};
0213 }
0214 
0215 void Json::fromJson(const QMetaObject *mo, const QJsonObject &obj, void *elem)
0216 {
0217     for (auto it = obj.begin(); it != obj.end(); ++it) {
0218         const auto idx = mo->indexOfProperty(it.key().toUtf8().constData());
0219         if (idx < 0) {
0220             continue;
0221         }
0222 
0223         const auto prop = mo->property(idx);
0224         if (!prop.isStored()) {
0225             continue;
0226         }
0227 
0228         if (prop.isFlagType() && it.value().isString()) {
0229             const auto key = prop.enumerator().keysToValue(it.value().toString().toUtf8().constData());
0230             prop.writeOnGadget(elem, key);
0231             continue;
0232         }
0233         if (prop.isEnumType() && it.value().isString()) { // internal enums in this QMO
0234             const auto key = prop.enumerator().keyToValue(it.value().toString().toUtf8().constData());
0235             prop.writeOnGadget(elem, key);
0236             continue;
0237         }
0238         if ((QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) && it.value().isString()) { // external enums
0239             const QMetaType mt(prop.userType());
0240             const auto mo = mt.metaObject();
0241             if (!mo) {
0242                 qCWarning(Log) << "No meta object found for enum type:" << prop.typeName();
0243                 continue;
0244             }
0245             const auto enumIdx = mo->indexOfEnumerator(prop.typeName() + strlen(mo->className()) + 2);
0246             if (enumIdx < 0) {
0247                 qCWarning(Log) << "Could not find QMetaEnum for" << prop.typeName();
0248                 continue;
0249             }
0250             const auto me = mo->enumerator(enumIdx);
0251             bool success = false;
0252             const auto numValue = me.keyToValue(it.value().toString().toUtf8().constData(), &success);
0253             if (!success) {
0254                 qCWarning(Log) << "Unknown enum value" << it.value().toString() << "for" << prop.typeName();
0255                 continue;
0256             }
0257             auto valueData = mt.create();
0258             *reinterpret_cast<int*>(valueData) = numValue;
0259             QVariant value(prop.metaType(), valueData);
0260             prop.writeOnGadget(elem, value);
0261             continue;
0262         }
0263 
0264         const auto v = variantFromJson(it.value(), prop.userType());
0265         prop.writeOnGadget(elem, v);
0266     }
0267 }