1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QTYPEDJSON_H |
5 | #define QTYPEDJSON_H |
6 | |
7 | // |
8 | // W A R N I N G |
9 | // ------------- |
10 | // |
11 | // This file is not part of the Qt API. It exists purely as an |
12 | // implementation detail. This header file may change from version to |
13 | // version without notice, or even be removed. |
14 | // |
15 | // We mean it. |
16 | // |
17 | |
18 | #include <QtCore/QObject> |
19 | #include <QtCore/QJsonValue> |
20 | #include <QtCore/QJsonObject> |
21 | #include <QtCore/QScopeGuard> |
22 | #include <QtCore/QSet> |
23 | #include <QtCore/QByteArray> |
24 | #include <QtCore/QMetaEnum> |
25 | #include <QtCore/QLoggingCategory> |
26 | #include <QtCore/qjsonvalue.h> |
27 | #include <QtCore/qjsonarray.h> |
28 | #include <QtCore/qjsonobject.h> |
29 | #include <QtJsonRpc/qtjsonrpcglobal.h> |
30 | |
31 | #include <functional> |
32 | #include <memory> |
33 | #include <typeinfo> |
34 | #include <optional> |
35 | |
36 | QT_BEGIN_NAMESPACE |
37 | |
38 | namespace QTypedJson { |
39 | Q_JSONRPC_EXPORT Q_DECLARE_LOGGING_CATEGORY(jsonRpcLog); |
40 | |
41 | Q_NAMESPACE |
42 | |
43 | enum class ObjectOption { None = 0, = 1, = 2 }; |
44 | Q_ENUM_NS(ObjectOption) |
45 | Q_DECLARE_FLAGS(ObjectOptions, ObjectOption) |
46 | Q_DECLARE_OPERATORS_FOR_FLAGS(ObjectOptions) |
47 | |
48 | enum class ParseMode { StopOnError }; |
49 | Q_ENUM_NS(ParseMode) |
50 | |
51 | enum class ParseStatus { Normal, Failed }; |
52 | Q_ENUM_NS(ParseStatus) |
53 | |
54 | template<typename... Ts> |
55 | using void_t = void; |
56 | |
57 | template<typename T, typename = void> |
58 | struct HasTypeName : std::false_type |
59 | { |
60 | }; |
61 | |
62 | template<typename T> |
63 | struct HasTypeName<T, void_t<decltype(T::TypeName)>> : std::true_type |
64 | { |
65 | }; |
66 | |
67 | template<typename T, typename = void> |
68 | struct HasMetaEnum : std::false_type |
69 | { |
70 | }; |
71 | |
72 | template<typename T> |
73 | struct HasMetaEnum<T, void_t<decltype(QMetaEnum::fromType<T>())>> : std::true_type |
74 | { |
75 | }; |
76 | |
77 | template<typename T> |
78 | const char *typeName() |
79 | { |
80 | if constexpr (HasTypeName<T>::value) |
81 | return T::TypeName; |
82 | else |
83 | return typeid(T).name(); |
84 | } |
85 | |
86 | template<typename T, typename = void> |
87 | struct JsonObjectOptions |
88 | { |
89 | static constexpr ObjectOptions value = ObjectOption::None; |
90 | }; |
91 | |
92 | template<typename T> |
93 | struct JsonObjectOptions<T, void_t<decltype(T::jsonObjectOptions)>> : std::true_type |
94 | { |
95 | static constexpr ObjectOptions value = T::jsonObjectOptions; |
96 | }; |
97 | |
98 | template<typename T, typename = void> |
99 | struct : std::false_type |
100 | { |
101 | }; |
102 | |
103 | template<typename T> |
104 | struct <T, void_t<decltype(std::declval<T>().extraFields())>> : std::true_type |
105 | { |
106 | }; |
107 | |
108 | template<typename T, typename = void> |
109 | struct : std::false_type |
110 | { |
111 | }; |
112 | |
113 | template<typename T> |
114 | struct < |
115 | T, void_t<decltype(std::declval<T>().setExtraFields(std::declval<QJsonObject>()))>> |
116 | : std::true_type |
117 | { |
118 | }; |
119 | |
120 | template<typename T, typename = void> |
121 | struct IsList : std::false_type |
122 | { |
123 | }; |
124 | |
125 | template<typename T> |
126 | struct IsList<T, void_t<typename T::value_type>> : std::true_type |
127 | { |
128 | }; |
129 | |
130 | template<typename T> |
131 | struct IsPointer : std::is_pointer<T> |
132 | { |
133 | }; |
134 | |
135 | template<typename T> |
136 | struct IsPointer<std::shared_ptr<T>> : std::true_type |
137 | { |
138 | }; |
139 | |
140 | template<typename T> |
141 | struct IsPointer<std::unique_ptr<T>> : std::true_type |
142 | { |
143 | }; |
144 | |
145 | template<typename T> |
146 | struct IsVariant : std::false_type |
147 | { |
148 | }; |
149 | |
150 | template<typename... Args> |
151 | struct IsVariant<std::variant<Args...>> : std::true_type |
152 | { |
153 | }; |
154 | |
155 | template<typename T> |
156 | inline QString enumToString(T value) |
157 | { |
158 | int iValue = int(value); |
159 | if constexpr (HasMetaEnum<T>::value) { |
160 | QMetaEnum metaEnum = QMetaEnum::fromType<T>(); |
161 | for (int i = 0; i < metaEnum.keyCount(); ++i) { |
162 | if (iValue == metaEnum.value(index: i)) |
163 | return QString::fromUtf8(utf8: metaEnum.key(index: i)); |
164 | } |
165 | } |
166 | return QString::number(iValue); |
167 | } |
168 | |
169 | template<typename T> |
170 | inline T enumFromString(const QString &value) |
171 | { |
172 | bool ok; |
173 | int v = value.toInt(ok: &ok); |
174 | if (ok) |
175 | return T(v); |
176 | if constexpr (HasMetaEnum<T>::value) { |
177 | QMetaEnum metaEnum = QMetaEnum::fromType<T>(); |
178 | for (int i = 0; i < metaEnum.keyCount(); ++i) { |
179 | if (value.compare(other: QLatin1String(metaEnum.key(index: i)), cs: Qt::CaseInsensitive) == 0) |
180 | return T { metaEnum.value(index: i) }; |
181 | } |
182 | } |
183 | return T {}; |
184 | } |
185 | |
186 | template<typename T> |
187 | inline QString enumToIntString(T value) |
188 | { |
189 | return QString::number(int(value)); |
190 | } |
191 | |
192 | template<typename T> |
193 | inline T enumFromIntString(const QString &value) |
194 | { |
195 | bool ok; |
196 | int v = value.toInt(ok: &ok); |
197 | if (ok) |
198 | return T(v); |
199 | return T {}; |
200 | } |
201 | |
202 | class Q_JSONRPC_EXPORT ValueStack |
203 | { |
204 | public: |
205 | QJsonValue value; |
206 | QString fieldPath; |
207 | int indexPath = -1; |
208 | int warnLevel = 0; |
209 | }; |
210 | |
211 | class ObjectStack |
212 | { |
213 | public: |
214 | const char *type; |
215 | ObjectOptions options; |
216 | QSet<QString> visitedFields; |
217 | }; |
218 | |
219 | class ReaderPrivate |
220 | { |
221 | public: |
222 | QList<ValueStack> valuesStack = {}; |
223 | QList<ObjectStack> objectsStack = {}; |
224 | ObjectOptions baseOptions = {}; |
225 | ParseMode parseMode = ParseMode::StopOnError; |
226 | ParseStatus parseStatus = ParseStatus::Normal; |
227 | QStringList errorMessages = {}; |
228 | }; |
229 | |
230 | class Q_JSONRPC_EXPORT Reader |
231 | { |
232 | public: |
233 | Reader(const QJsonValue &v); |
234 | ~Reader(); |
235 | |
236 | QStringList errorMessages(); |
237 | void clearErrorMessages(); |
238 | |
239 | // serialization templates |
240 | |
241 | template<typename T> |
242 | bool startObject(const char *type, ObjectOptions options, quintptr id, T &) |
243 | { |
244 | return this->startObjectF(type, options, id); |
245 | } |
246 | template<typename T> |
247 | void endObject(const char *type, ObjectOptions options, quintptr id, T &obj); |
248 | |
249 | template<typename T> |
250 | bool startArray(qint32 &size, T &el) |
251 | { |
252 | startArrayF(size); |
253 | using BaseT = std::decay_t<T>; |
254 | if constexpr (std::is_base_of_v<QList<typename BaseT::value_type>, BaseT>) { |
255 | el.resize(size); |
256 | } else { |
257 | assert(false); // currently unsupported |
258 | } |
259 | return true; |
260 | } |
261 | |
262 | template<typename T> |
263 | bool handleOptional(T &el) |
264 | { |
265 | bool isMissing = currentValue().isUndefined() || currentValue().isNull(); |
266 | if (isMissing) |
267 | el.reset(); |
268 | else |
269 | el.emplace(); |
270 | return bool(el); |
271 | } |
272 | |
273 | template<typename T> |
274 | bool handlePointer(T &el) |
275 | { |
276 | bool isMissing = currentValue().isUndefined() || currentValue().isNull(); |
277 | if (isMissing) |
278 | el = nullptr; |
279 | else |
280 | el = T(new std::decay_t<decltype(*el)>); |
281 | return bool(el); |
282 | } |
283 | |
284 | template<typename... T> |
285 | void handleVariant(std::variant<T...> &el) |
286 | { |
287 | std::tuple<T...> options; |
288 | int status = 0; |
289 | ReaderPrivate origStatus = *m_p; |
290 | QStringList err; |
291 | auto tryRead = [this, &origStatus, &status, &el, &err](auto &x) { |
292 | if (status == 2) |
293 | return; |
294 | if (status == 1) |
295 | *this->m_p = origStatus; |
296 | else |
297 | status = 1; |
298 | doWalk(*this, x); |
299 | if (m_p->parseStatus == ParseStatus::Normal) { |
300 | status = 2; |
301 | el = x; |
302 | return; |
303 | } |
304 | err.append(QStringLiteral(u"Type %1 failed with errors:" ) |
305 | .arg(a: QLatin1String(typeid(decltype(x)).name()))); |
306 | err += m_p->errorMessages; |
307 | }; |
308 | std::apply([&tryRead](auto &...x) { (..., tryRead(x)); }, options); |
309 | if (status == 1) { |
310 | m_p->errorMessages.clear(); |
311 | m_p->errorMessages.append(QStringLiteral(u"All options of variant failed:" )); |
312 | m_p->errorMessages += err; |
313 | } |
314 | } |
315 | |
316 | template<typename T> |
317 | void handleEnum(T &e) |
318 | { |
319 | if (currentValue().isDouble()) { |
320 | e = T(currentValue().toInt()); |
321 | } else { |
322 | e = enumFromString<T>(currentValue().toString()); |
323 | } |
324 | } |
325 | |
326 | template<typename T> |
327 | void endArray(qint32 &size, T &) |
328 | { |
329 | this->endArrayF(size); |
330 | } |
331 | |
332 | // serialization callbacks |
333 | void handleBasic(bool &); |
334 | void handleBasic(QByteArray &); |
335 | void handleBasic(int &); |
336 | void handleBasic(double &); |
337 | void handleNullType(); |
338 | void handleJson(QJsonValue &v); |
339 | void handleJson(QJsonObject &v); |
340 | void handleJson(QJsonArray &v); |
341 | bool startField(const QString &fieldName); |
342 | bool startField(const char *fieldName); |
343 | void endField(const QString &fieldName); |
344 | void endField(const char *fieldName); |
345 | bool startElement(qint32 index); |
346 | void endElement(qint32 index); |
347 | bool startTuple(qint32 size); |
348 | void endTuple(qint32 size); |
349 | |
350 | private: |
351 | void (const QJsonObject &e); |
352 | void warnMissing(QStringView s); |
353 | void warnNonNull(); |
354 | void warnInvalidSize(qint32 size, qint32 expectedSize); |
355 | void warn(const QString &msg); |
356 | QJsonObject () const; |
357 | bool startObjectF(const char *type, ObjectOptions options, quintptr id); |
358 | void endObjectF(const char *type, ObjectOptions options, quintptr id); |
359 | void startArrayF(qint32 &size); |
360 | void endArrayF(qint32 &size); |
361 | bool hasElement(); |
362 | QString currentPath() const; |
363 | const QJsonValue ¤tValue() const { return m_p->valuesStack.last().value; } |
364 | ReaderPrivate *m_p; |
365 | }; |
366 | |
367 | template<typename T, typename = void> |
368 | struct HasWalk : std::false_type |
369 | { |
370 | }; |
371 | |
372 | template<typename T> |
373 | struct HasWalk<T, void_t<decltype(T {}.walk(std::declval<Reader>))>> : std::true_type |
374 | { |
375 | }; |
376 | |
377 | template<typename W, typename C, typename T> |
378 | void field(W &w, const C &fieldName, T &el) |
379 | { |
380 | if (w.startField(fieldName)) { |
381 | auto guard = qScopeGuard([&w, &fieldName]() { w.endField(fieldName); }); |
382 | doWalk(w, el); |
383 | } |
384 | } |
385 | |
386 | template<typename W, typename T> |
387 | inline void doWalk(W &w, T &el) |
388 | { |
389 | using BaseT = std::decay_t<T>; |
390 | if constexpr ( |
391 | std::is_same_v< |
392 | BaseT, |
393 | int> || std::is_same_v<BaseT, double> || std::is_same_v<BaseT, bool> || std::is_same_v<BaseT, QByteArray>) { |
394 | w.handleBasic(el); |
395 | } else if constexpr (HasWalk<BaseT>::value) { |
396 | const char *type = typeName<BaseT>(); |
397 | ObjectOptions options = JsonObjectOptions<BaseT>::value; |
398 | quintptr id = quintptr(&el); |
399 | if (w.startObject(type, options, id, el)) { |
400 | el.walk(w); |
401 | w.endObject(type, options, id, el); |
402 | } |
403 | } else if constexpr ( |
404 | std::is_same_v< |
405 | BaseT, |
406 | QJsonValue> || std::is_same_v<BaseT, QJsonObject> || std::is_same_v<BaseT, QJsonArray>) { |
407 | w.handleJson(el); |
408 | } else if constexpr (std::is_enum_v<BaseT>) { |
409 | w.handleEnum(el); |
410 | } else if constexpr (std::is_same_v<std::nullptr_t, BaseT>) { |
411 | w.handleNullType(); |
412 | } else if constexpr (IsPointer<BaseT>::value) { |
413 | if (w.handlePointer(el) && el) |
414 | doWalk(w, *el); |
415 | } else if constexpr (IsVariant<BaseT>::value) { |
416 | w.handleVariant(el); |
417 | } else if constexpr (IsList<BaseT>::value) { |
418 | if constexpr (std::is_same_v<std::optional<typename BaseT::value_type>, BaseT>) { |
419 | if (w.handleOptional(el) && el) |
420 | doWalk(w, *el); |
421 | } else { |
422 | int size = el.size(); |
423 | if (!w.startArray(size, el)) |
424 | return; |
425 | int i = 0; |
426 | for (auto &subEl : el) { |
427 | if (!w.startElement(i)) |
428 | break; |
429 | doWalk(w, subEl); |
430 | w.endElement(i); |
431 | ++i; |
432 | } |
433 | w.endArray(size, el); |
434 | } |
435 | } else { |
436 | qWarning() << "Unhandled type" << typeid(T).name(); |
437 | assert(false); |
438 | } |
439 | } |
440 | |
441 | template<typename T> |
442 | inline void Reader::endObject(const char *type, ObjectOptions options, quintptr id, T &obj) |
443 | { |
444 | using BaseT = std::decay_t<T>; |
445 | QJsonObject ; |
446 | if (SetExtraFields<BaseT>::value |
447 | || (options & (ObjectOption::KeepExtraFields | ObjectOption::WarnExtra))) |
448 | extra = this->getExtraFields(); |
449 | this->endObjectF(type, options, id); |
450 | if constexpr (SetExtraFields<BaseT>::value) |
451 | obj.setExtraFields(extra); |
452 | else if (extra.constBegin() != extra.constEnd()) |
453 | warnExtra(e: extra); |
454 | } |
455 | |
456 | class Q_JSONRPC_EXPORT JsonBuilder |
457 | { |
458 | public: |
459 | JsonBuilder() = default; |
460 | |
461 | // public api |
462 | QJsonValue popLastValue(); |
463 | |
464 | // serialization templates |
465 | template<typename T> |
466 | bool handleOptional(T &el) |
467 | { |
468 | if (el) |
469 | return true; |
470 | this->handleMissingOptional(); |
471 | return false; |
472 | } |
473 | |
474 | template<typename T> |
475 | bool handlePointer(T &el) |
476 | { |
477 | return bool(el); |
478 | } |
479 | |
480 | template<typename T> |
481 | bool startObject(const char *type, ObjectOptions options, quintptr id, T &) |
482 | { |
483 | return this->startObjectF(type, options, id); |
484 | } |
485 | |
486 | template<typename T> |
487 | void endObject(const char *type, ObjectOptions options, quintptr id, T &) |
488 | { |
489 | this->endObjectF(type, options, id); |
490 | } |
491 | |
492 | template<typename T> |
493 | bool startArray(qint32 &size, T &el) |
494 | { |
495 | using BaseT = std::decay_t<T>; |
496 | if constexpr (std::is_base_of_v<QList<typename BaseT::value_type>, BaseT>) { |
497 | size = el.size(); |
498 | } else { |
499 | assert(false); // currently unsupported |
500 | } |
501 | return startArrayF(size); |
502 | } |
503 | |
504 | template<typename T> |
505 | void endArray(qint32 &size, T &) |
506 | { |
507 | this->endArrayF(size); |
508 | } |
509 | |
510 | template<typename T> |
511 | void handleVariant(T &el) |
512 | { |
513 | std::visit([this](auto &v) { doWalk(*this, v); }, el); |
514 | } |
515 | |
516 | template<typename T> |
517 | void handleEnum(T &el) |
518 | { |
519 | QString eVal = enumToString(el); |
520 | bool ok; |
521 | int value = eVal.toInt(ok: &ok); |
522 | if (ok) |
523 | this->handleBasic(v: value); |
524 | else |
525 | this->handleBasic(v: eVal.toUtf8()); |
526 | } |
527 | |
528 | // serialization callbacks |
529 | void handleBasic(const bool &v); |
530 | void handleBasic(const QByteArray &v); |
531 | void handleBasic(const int &v); |
532 | void handleBasic(const double &v); |
533 | void handleNullType(); |
534 | void handleJson(QJsonValue &v); |
535 | void handleJson(QJsonObject &v); |
536 | void handleJson(QJsonArray &v); |
537 | bool startField(const QString &fieldName); |
538 | bool startField(const char *fieldName); |
539 | void endField(const QString &); |
540 | void endField(const char *); |
541 | bool startElement(qint32 index); |
542 | void endElement(qint32); |
543 | bool startTuple(qint32 size); |
544 | void endTuple(qint32 size); |
545 | |
546 | private: |
547 | void handleMissingOptional(); |
548 | bool startObjectF(const char *, ObjectOptions, quintptr); |
549 | void endObjectF(const char *, ObjectOptions, quintptr); |
550 | bool startArrayF(qint32 &); |
551 | void endArrayF(qint32 &); |
552 | |
553 | QList<qsizetype> m_fieldLevel; |
554 | QList<qsizetype> m_arrayLevel; |
555 | QList<std::variant<QJsonObject, QJsonArray, QJsonValue>> m_values; |
556 | }; |
557 | |
558 | template<typename W, typename... Params> |
559 | void doWalkArgs(W &w, Params... params) |
560 | { |
561 | if constexpr (sizeof...(Params) == 0) { |
562 | } else if constexpr (sizeof...(Params) == 1) { |
563 | doWalk(w, params...); |
564 | } else { |
565 | if (!w.startTuple(sizeof...(Params))) |
566 | return; |
567 | qint32 i = 0; |
568 | bool skipRest = false; |
569 | std::apply( |
570 | [&i, &w, &skipRest](auto &el) { |
571 | if (skipRest || !w.startElement(i)) { |
572 | skipRest = true; |
573 | } else { |
574 | doWalk(w, el); |
575 | w.endElement(i++); |
576 | } |
577 | }, |
578 | params...); |
579 | w.endTuple(sizeof...(Params)); |
580 | } |
581 | } |
582 | |
583 | template<typename... Params> |
584 | QJsonValue toJsonValue(Params... params) |
585 | { |
586 | JsonBuilder b; |
587 | doWalkArgs(b, params...); |
588 | return b.popLastValue(); |
589 | } |
590 | |
591 | } // namespace QTypedJson |
592 | QT_END_NAMESPACE |
593 | #endif // QTYPEDJSON_H |
594 | |