KPublicTransport

json.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "json_p.h"
8#include "logging.h"
9
10#include <QColor>
11#include <QDateTime>
12#include <QDebug>
13#include <QLocale>
14#include <QMetaObject>
15#include <QMetaProperty>
16#include <QRectF>
17#include <QTimeZone>
18#include <QUrl>
19#include <QVariant>
20
21#include <cmath>
22
23using namespace KPublicTransport;
24
25QString Json::translatedValue(const QJsonObject &obj, const QString &key)
26{
27 auto languageWithCountry = QLocale().name();
28 auto it = obj.constFind(key + QLatin1Char('[') + languageWithCountry + QLatin1Char(']'));
29 if (it != obj.constEnd()) {
30 return it.value().toString();
31 }
32 const auto language = QStringView(languageWithCountry).mid(0, languageWithCountry.indexOf(QLatin1Char('_')));
33 it = obj.constFind(key + QLatin1Char('[') + language + QLatin1Char(']'));
34 if (it != obj.constEnd()) {
35 return it.value().toString();
36 }
37 return obj.value(key).toString();
38}
39
40QStringList Json::toStringList(const QJsonValue &v)
41{
42 const auto a = v.toArray();
44 l.reserve(a.size());
45 for (const auto &av : a) {
46 l.push_back(av.toString());
47 }
48 return l;
49}
50
51static QJsonValue variantToJson(const QVariant &v)
52{
53 switch (v.userType()) {
55 {
56 const auto s = v.toString();
57 return s.isNull() ? QJsonValue() : v.toString();
58 }
61 {
62 auto d = v.toDouble();
63 if (std::isnan(d)) {
64 return QJsonValue::Null;
65 }
66 return d;
67 }
68 case QMetaType::Int:
69 return v.toInt();
71 {
72 const auto dt = v.toDateTime();
73 if (!dt.isValid()) {
74 return {};
75 }
76 if (dt.timeSpec() == Qt::TimeZone && dt.timeZone() != QTimeZone::utc()) {
77 QJsonObject dtObj;
78 dtObj.insert(QLatin1String("value"), dt.toString(Qt::ISODate));
79 dtObj.insert(QLatin1String("timezone"), QString::fromUtf8(dt.timeZone().id()));
80 return dtObj;
81 }
83 }
84 case QMetaType::QUrl:
85 {
86 const auto url = v.toUrl();
87 return url.isValid() ? url.toString() : QJsonValue();
88 }
90 {
91 const auto r = v.toRectF();
92 QJsonObject obj;
93 obj.insert(QLatin1String("x1"), r.topLeft().x());
94 obj.insert(QLatin1String("y1"), r.topLeft().y());
95 obj.insert(QLatin1String("x2"), r.bottomRight().x());
96 obj.insert(QLatin1String("y2"), r.bottomRight().y());
97 return obj;
98 }
100 {
101 const auto c = v.value<QColor>();
102 return c.isValid() ? v.value<QColor>().name() : QJsonValue();;
103 }
104 case QMetaType::Bool:
105 return v.toBool();
106 }
107
108 if (QMetaType mt(v.userType()); mt.metaObject() && (mt.flags() & QMetaType::IsEnumeration)) {
109 return v.toString();
110 }
111
112 if (v.canConvert<QVariantList>()) {
113 const auto l = v.toList();
114 if (l.isEmpty()) {
115 return {};
116 }
117
118 QJsonArray a;
119 std::transform(l.begin(), l.end(), std::back_inserter(a), variantToJson);
120 return a;
121 }
122
123 return {};
124}
125
126QJsonObject Json::toJson(const QMetaObject *mo, const void *elem)
127{
128 QJsonObject obj;
129
130 for (int i = 0; i < mo->propertyCount(); ++i) {
131 const auto prop = mo->property(i);
132 if (!prop.isStored() || !prop.isWritable()) {
133 continue;
134 }
135
136 if (prop.isFlagType()) { // flag has to come first, as prop.isEnumType() is also true for this
137 const auto key = prop.readOnGadget(elem).toInt();
138 const auto value = prop.enumerator().valueToKeys(key);
139 obj.insert(QString::fromUtf8(prop.name()), QString::fromUtf8(value));
140 continue;
141 }
142 if (prop.isEnumType()) { // enums defined in this QMO
143 const auto key = prop.readOnGadget(elem).toInt();
144 const auto value = prop.enumerator().valueToKey(key);
145 obj.insert(QString::fromUtf8(prop.name()), QString::fromUtf8(value));
146 continue;
147 } else if (QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) { // external enums
148 obj.insert(QString::fromUtf8(prop.name()), prop.readOnGadget(elem).toString());
149 continue;
150 }
151
152 const auto v = variantToJson(prop.readOnGadget(elem));
153 if (!v.isNull()) {
154 obj.insert(QString::fromUtf8(prop.name()), v);
155 }
156 }
157
158 return obj;
159}
160
161// cache timezones by IANA id, with Qt6 QTimeZone(QByteArray) is unreasonably slow
162// on Android, so that loading Itinerary's cached public transport data takes up to 20secs...
163// can and should be removed once this has been fixed in Qt
164static QTimeZone timeZone(const QByteArray &tzId)
165{
166 static QHash<QByteArray, QTimeZone> s_tzCache;
167 const auto it = s_tzCache.constFind(tzId);
168 if (it != s_tzCache.constEnd()) {
169 return it.value();
170 }
171 auto tz = QTimeZone(tzId);
172 s_tzCache.insert(tzId, tz);
173 return tz;
174}
175
176static QVariant variantFromJson(const QJsonValue &v, int mt)
177{
178 switch (mt) {
180 return v.toString();
182 case QMetaType::Float:
183 if (v.isString()) {
184 return v.toString().toDouble();
185 }
186 return v.toDouble();
187 case QMetaType::Int:
188 if (v.isString()) {
189 return v.toString().toInt();
190 }
191 return v.toInt();
193 {
194 if (v.isObject()) {
195 const auto dtObj = v.toObject();
196 auto dt = QDateTime::fromString(dtObj.value(QLatin1String("value")).toString(), Qt::ISODate);
197 dt.setTimeZone(timeZone(dtObj.value(QLatin1String("timezone")).toString().toUtf8()));
198 return dt;
199 }
201 }
202 case QMetaType::QUrl:
203 return QUrl(v.toString());
205 return Json::toStringList(v);
207 {
208 const auto obj = v.toObject();
209 QRectF r;
210 r.setTopLeft(QPointF(obj.value(QLatin1String("x1")).toDouble(), obj.value(QLatin1String("y1")).toDouble()));
211 r.setBottomRight(QPointF(obj.value(QLatin1String("x2")).toDouble(), obj.value(QLatin1String("y2")).toDouble()));
212 return r;
213 }
215 return QColor(v.toString());
216 }
217
218 return {};
219}
220
221void Json::fromJson(const QMetaObject *mo, const QJsonObject &obj, void *elem)
222{
223 for (auto it = obj.begin(); it != obj.end(); ++it) {
224 const auto idx = mo->indexOfProperty(it.key().toUtf8().constData());
225 if (idx < 0) {
226 continue;
227 }
228
229 const auto prop = mo->property(idx);
230 if (!prop.isStored() || !prop.isWritable()) {
231 continue;
232 }
233
234 if (prop.isFlagType() && it.value().isString()) {
235 const auto key = prop.enumerator().keysToValue(it.value().toString().toUtf8().constData());
236 prop.writeOnGadget(elem, key);
237 continue;
238 }
239 if (prop.isEnumType() && it.value().isString()) { // internal enums in this QMO
240 bool ok = false;
241 const auto key = prop.enumerator().keyToValue(it.value().toString().toUtf8().constData(), &ok);
242 if (ok) {
243 prop.writeOnGadget(elem, key);
244 } else {
245 qCWarning(Log) << "Got invalid enum key:" << it.value().toString() << "for" << prop.typeName();
246 }
247 continue;
248 }
249 if ((QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) && it.value().isString()) { // external enums
250 const QMetaType mt(prop.userType());
251 const auto mo = mt.metaObject();
252 if (!mo) {
253 qCWarning(Log) << "No meta object found for enum type:" << prop.typeName();
254 continue;
255 }
256 const auto enumIdx = mo->indexOfEnumerator(prop.typeName() + strlen(mo->className()) + 2);
257 if (enumIdx < 0) {
258 qCWarning(Log) << "Could not find QMetaEnum for" << prop.typeName();
259 continue;
260 }
261 const auto me = mo->enumerator(enumIdx);
262 bool success = false;
263 const auto numValue = me.keyToValue(it.value().toString().toUtf8().constData(), &success);
264 if (!success) {
265 qCWarning(Log) << "Unknown enum value" << it.value().toString() << "for" << prop.typeName();
266 continue;
267 }
268 auto valueData = mt.create();
269 *reinterpret_cast<int*>(valueData) = numValue;
270 QVariant value(prop.metaType(), valueData);
271 prop.writeOnGadget(elem, value);
272 continue;
273 }
274
275 const auto v = variantFromJson(it.value(), prop.userType());
276 prop.writeOnGadget(elem, v);
277 }
278}
char * toString(const EngineQuery &query)
Query operations and data types for accessing realtime public transport information from online servi...
bool isValid() const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
QString toString(QStringView format, QCalendar cal) const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator insert(const Key &key, const T &value)
iterator begin()
const_iterator constEnd() const const
const_iterator constFind(QLatin1StringView key) const const
iterator end()
iterator insert(QLatin1StringView key, const QJsonValue &value)
QJsonValue value(QLatin1StringView key) const const
bool isObject() const const
bool isString() const const
QJsonArray toArray() const const
double toDouble(double defaultValue) const const
int toInt(int defaultValue) const const
QJsonObject toObject() const const
QString toString() const const
iterator begin()
iterator end()
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
QString name() const const
int keyToValue(const char *key, bool *ok) const const
const char * className() const const
QMetaEnum enumerator(int index) const const
int indexOfEnumerator(const char *name) const const
int indexOfProperty(const char *name) const const
QMetaProperty property(int index) const const
int propertyCount() const const
TypeFlags flags() const const
const QMetaObject * metaObject() const const
void setBottomRight(const QPointF &position)
void setTopLeft(const QPointF &position)
QString fromUtf8(QByteArrayView str)
bool isNull() const const
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
QStringView mid(qsizetype start, qsizetype length) const const
TimeZone
QTimeZone utc()
bool isValid() const const
bool canConvert() const const
bool isNull() const const
bool toBool() const const
QDateTime toDateTime() const const
double toDouble(bool *ok) const const
int toInt(bool *ok) const const
QList< QVariant > toList() const const
QRectF toRectF() const const
QString toString() const const
QUrl toUrl() const const
int userType() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:57:19 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.