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) {
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 return v.toDouble();
184 case QMetaType::Int:
185 return v.toInt();
187 {
188 if (v.isObject()) {
189 const auto dtObj = v.toObject();
190 auto dt = QDateTime::fromString(dtObj.value(QLatin1String("value")).toString(), Qt::ISODate);
191 dt.setTimeZone(timeZone(dtObj.value(QLatin1String("timezone")).toString().toUtf8()));
192 return dt;
193 }
195 }
196 case QMetaType::QUrl:
197 return QUrl(v.toString());
199 return Json::toStringList(v);
201 {
202 const auto obj = v.toObject();
203 QRectF r;
204 r.setTopLeft(QPointF(obj.value(QLatin1String("x1")).toDouble(), obj.value(QLatin1String("y1")).toDouble()));
205 r.setBottomRight(QPointF(obj.value(QLatin1String("x2")).toDouble(), obj.value(QLatin1String("y2")).toDouble()));
206 return r;
207 }
209 return QColor(v.toString());
210 }
211
212 return {};
213}
214
215void Json::fromJson(const QMetaObject *mo, const QJsonObject &obj, void *elem)
216{
217 for (auto it = obj.begin(); it != obj.end(); ++it) {
218 const auto idx = mo->indexOfProperty(it.key().toUtf8().constData());
219 if (idx < 0) {
220 continue;
221 }
222
223 const auto prop = mo->property(idx);
224 if (!prop.isStored() || !prop.isWritable()) {
225 continue;
226 }
227
228 if (prop.isFlagType() && it.value().isString()) {
229 const auto key = prop.enumerator().keysToValue(it.value().toString().toUtf8().constData());
230 prop.writeOnGadget(elem, key);
231 continue;
232 }
233 if (prop.isEnumType() && it.value().isString()) { // internal enums in this QMO
234 const auto key = prop.enumerator().keyToValue(it.value().toString().toUtf8().constData());
235 prop.writeOnGadget(elem, key);
236 continue;
237 }
238 if ((QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) && it.value().isString()) { // external enums
239 const QMetaType mt(prop.userType());
240 const auto mo = mt.metaObject();
241 if (!mo) {
242 qCWarning(Log) << "No meta object found for enum type:" << prop.typeName();
243 continue;
244 }
245 const auto enumIdx = mo->indexOfEnumerator(prop.typeName() + strlen(mo->className()) + 2);
246 if (enumIdx < 0) {
247 qCWarning(Log) << "Could not find QMetaEnum for" << prop.typeName();
248 continue;
249 }
250 const auto me = mo->enumerator(enumIdx);
251 bool success = false;
252 const auto numValue = me.keyToValue(it.value().toString().toUtf8().constData(), &success);
253 if (!success) {
254 qCWarning(Log) << "Unknown enum value" << it.value().toString() << "for" << prop.typeName();
255 continue;
256 }
257 auto valueData = mt.create();
258 *reinterpret_cast<int*>(valueData) = numValue;
259 QVariant value(prop.metaType(), valueData);
260 prop.writeOnGadget(elem, value);
261 continue;
262 }
263
264 const auto v = variantFromJson(it.value(), prop.userType());
265 prop.writeOnGadget(elem, v);
266 }
267}
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
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
void setBottomRight(const QPointF &position)
void setTopLeft(const QPointF &position)
QString fromUtf8(QByteArrayView str)
bool isNull() const const
QStringView mid(qsizetype start, qsizetype length) const const
TimeZone
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 Tue Mar 26 2024 11:13:06 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.