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 }