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
221[[nodiscard]] static std::optional<int> enumKeyToValue(const QMetaEnum &me, const QString &key)
222{
223 bool ok = false;
224 const auto value = me.keyToValue(key.toUtf8().constData(), &ok);
225 if (ok) {
226 return value;
227 }
228
229 // try harder by case-insensitive search
230 // shouldn't happen under normal circumstances, but since we also run through here for
231 // data from onboard API scripts this adds some extra robustness
232 for (int i = 0; i < me.keyCount(); ++i) {
234 return me.value(i);
235 }
236 }
237
238 qCWarning(Log) << "Got invalid enum key:" << key << "for" << me.enumName();
239 return {};
240}
241
242void Json::fromJson(const QMetaObject *mo, const QJsonObject &obj, void *elem)
243{
244 for (auto it = obj.begin(); it != obj.end(); ++it) {
245 const auto idx = mo->indexOfProperty(it.key().toUtf8().constData());
246 if (idx < 0) {
247 continue;
248 }
249
250 const auto prop = mo->property(idx);
251 if (!prop.isStored() || !prop.isWritable()) {
252 continue;
253 }
254
255 if (prop.isFlagType() && it.value().isString()) {
256 const auto key = prop.enumerator().keysToValue(it.value().toString().toUtf8().constData());
257 prop.writeOnGadget(elem, key);
258 continue;
259 }
260 if (prop.isEnumType() && it.value().isString()) { // internal enums in this QMO
261 const auto value = enumKeyToValue(prop.enumerator(), it.value().toString());
262 if (value) {
263 prop.writeOnGadget(elem, *value);
264 }
265 continue;
266 }
267 if ((QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) && it.value().isString()) { // external enums
268 const QMetaType mt(prop.userType());
269 const auto mo = mt.metaObject();
270 if (!mo) {
271 qCWarning(Log) << "No meta object found for enum type:" << prop.typeName();
272 continue;
273 }
274 const auto enumIdx = mo->indexOfEnumerator(prop.typeName() + strlen(mo->className()) + 2);
275 if (enumIdx < 0) {
276 qCWarning(Log) << "Could not find QMetaEnum for" << prop.typeName();
277 continue;
278 }
279 const auto me = mo->enumerator(enumIdx);
280 const auto numValue = enumKeyToValue(me, it.value().toString());
281 if (numValue) {
282 auto valueData = mt.create();
283 *reinterpret_cast<int*>(valueData) = *numValue;
284 QVariant value(prop.metaType(), valueData);
285 prop.writeOnGadget(elem, value);
286 }
287 continue;
288 }
289
290 const auto v = variantFromJson(it.value(), prop.userType());
291 prop.writeOnGadget(elem, v);
292 }
293}
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
const char * enumName() const const
const char * key(int index) const const
int keyCount() const const
int keyToValue(const char *key, bool *ok) const const
int value(int index) 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)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
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
CaseInsensitive
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-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:53:27 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.