Qx v0.7
Qt Extensions Library
Loading...
Searching...
No Matches
qx-json.h
Go to the documentation of this file.
1#ifndef QX_JSON_H
2#define QX_JSON_H
3
4// Shared Lib Support
5#include "qx/core/qx_core_export.h"
6
7// Qt Includes
8#include <QString>
9#include <QJsonValueRef>
10#include <QJsonObject>
11#include <QJsonArray>
12#include <QJsonDocument>
13#include <QFile>
14#include <QFileInfo>
15
16// Intra-component Includes
18#include "qx/core/qx-error.h"
19
20// Extra-component Includes
23
24//-Macros------------------------------------------------------------------
26#define __QX_JSON_META_STRUCT_INSIDE(meta_tuple) \
27template <typename StructT> \
28 struct QxJsonMetaStructInside \
29{ \
30 static inline constexpr auto memberMetadata() \
31 { \
32 return meta_tuple; \
33 } \
34}
35
36#define __QX_JSON_META_STRUCT_OUTSIDE(self_type, meta_tuple) \
37namespace QxJson \
38{ \
39 template <typename StructT> \
40 struct QxJsonMetaStructOutside<self_type, StructT> \
41 { \
42 static inline constexpr auto memberMetadata() \
43 { \
44 return meta_tuple; \
45 } \
46 }; \
47}
48
50
51/* TODO: See if there is a magic way to have QX_JSON_STRUCT_X only require QX_JSON_MEMBER_ALIASED
52 * for each member the user wants to be aliased, but not require QX_JSON_MEMBER for members that
53 * are to have a default name
54 */
55#define QX_JSON_MEMBER(member) QxJsonPrivate::makeMemberMetadata<#member>(&StructT::member)
56#define QX_JSON_MEMBER_ALIASED(member, key) QxJsonPrivate::makeMemberMetadata<key>(&StructT::member)
57
58#define QX_JSON_STRUCT(...) __QX_JSON_META_STRUCT_INSIDE(std::make_tuple(QX_FOR_EACH_DELIM(QX_JSON_MEMBER, __VA_ARGS__)))
59#define QX_JSON_STRUCT_X(...) __QX_JSON_META_STRUCT_INSIDE(std::make_tuple(__VA_ARGS__))
60
61#define QX_JSON_STRUCT_OUTSIDE(Struct, ...) __QX_JSON_META_STRUCT_OUTSIDE(Struct, std::make_tuple(QX_FOR_EACH_DELIM(QX_JSON_MEMBER, __VA_ARGS__)))
62#define QX_JSON_STRUCT_OUTSIDE_X(Struct, ...) __QX_JSON_META_STRUCT_OUTSIDE(Struct, std::make_tuple(__VA_ARGS__))
63
64/*
65 * TODO: Create an "inside" version of this macro and its underlying functions, though it's tricky
66 * given clang/GCCs issues with non-namespace explicit template specializations so either a dummy
67 * template parameter will need to be used (to make it technically a partial specialization) or
68 * the specializations themselves will still need to be outside the struct (second macro), while
69 * the declaration of the inner override struct (first macro, e.g. QX_JSON_DECLARE_MEMBER_OVERRIDES(); )
70 * is inside
71 */
72#define QX_JSON_MEMBER_OVERRIDE(Struct, member, ...) \
73namespace QxJson \
74{ \
75 template<> \
76 struct MemberOverrideCoverter<Struct, #member> \
77 { \
78 __VA_ARGS__\
79 }; \
80}
81
82namespace QxJson
83{
84
85class QX_CORE_EXPORT File
86{
87private:
88 QString mIdentifier;
89 QString mFileError;
90
91public:
92 File(const QString& filename, const QString& fileError = {});
93 File(const QFile& docFile, const QString& fileError = {});
94 File(const QFileInfo& docFile, const QString& fileError = {});
95
96 QString string() const;
97};
98
99class QX_CORE_EXPORT Data
100{
101private:
102 QString mDataError;
103
104public:
105 Data(const QString& dataError = {});
106
107 QString string() const;
108};
109
110class QX_CORE_EXPORT Document
111{
112private:
113 QString mName;
114
115public:
116 Document(const QString& name = {});
117
118 QString string() const;
119};
120
121class QX_CORE_EXPORT Object
122{
123public:
124 Object();
125
126 QString string() const;
127};
128
129class QX_CORE_EXPORT ObjectKey
130{
131private:
132 QString mName;
133
134public:
135 ObjectKey(const QString& name);
136
137 QString string() const;
138};
139
140class QX_CORE_EXPORT Array
141{
142public:
143 Array();
144
145 QString string() const;
146};
147
148class QX_CORE_EXPORT ArrayElement
149{
150private:
151 uint mElement;
152
153public:
154 ArrayElement(uint element);
155
156 QString string() const;
157};
158
159using ContextNode = std::variant<File, Data, Document, Object, ObjectKey, Array, ArrayElement>;
160
161} // namespace QxJson
162
163namespace Qx
164{
165
166class QX_CORE_EXPORT JsonError final : public AbstractError<"Qx::JsonError", 5>
167{
168//-Class Enums-------------------------------------------------------------
169public:
183
184//-Class Variables-------------------------------------------------------------
185private:
186 static inline const QHash<Form, QString> ERR_STRINGS{
187 {NoError, u""_s},
188 {MissingKey, u"The key does not exist."_s},
189 {TypeMismatch, u"Value type mismatch."_s},
190 {EmptyDoc, u"The document is empty."_s},
191 {InvalidValue, u"Invalid value for type."_s},
192 {InvalidData, u"Data parse error."_s},
193 {MissingFile, u"File does not exist."_s},
194 {InaccessibleFile, u"Cannot open the file."_s},
195 {FileReadError, u"File read error."_s},
196 {FileWriteError, u"File write error."_s}
197 };
198
199//-Instance Variables-------------------------------------------------------------
200private:
201 QString mAction;
202 Form mForm;
203 QList<QxJson::ContextNode> mContext;
204
205//-Constructor-----------------------------------------------------------------
206public:
207 JsonError();
208 JsonError(const QString& a, Form f);
209
210//-Instance Functions-------------------------------------------------------------
211private:
212 quint32 deriveValue() const override;
213 QString derivePrimary() const override;
214 QString deriveSecondary() const override;
215 QString deriveDetails() const override;
216
217public:
218 bool isValid() const;
219 QString action() const;
220 Form form() const;
221 QList<QxJson::ContextNode> context() const;
222 JsonError& withContext(const QxJson::ContextNode& node);
223};
224
225} // namespace Qx
226
227namespace QxJson
228{
229//-Structs---------------------------------------------------------------
230template<typename T>
232
233template<class Struct, Qx::CStringLiteral member>
234struct MemberOverrideCoverter;
235
236template<typename SelfType, typename DelayedSelfType>
237struct QxJsonMetaStructOutside;
238
239//-Concepts--------------------------------------------------------------
240
241template<typename T>
243
244template<typename T>
245concept json_struct_inside = requires {
246 T::template QxJsonMetaStructInside<T>::memberMetadata();
247};
248
249template<typename T>
250concept json_struct_outside = requires {
251 QxJsonMetaStructOutside<T, T>::memberMetadata();
252};
253
254template<typename T>
257
258template<typename T>
259concept json_convertible = requires(T& tValue) {
260 { Converter<T>::fromJson(tValue, QJsonValue()) } -> std::same_as<Qx::JsonError>;
261 { Converter<T>::toJson(tValue) } -> qjson_type;
262};
263
264template<class K, typename T, Qx::CStringLiteral N>
265concept json_override_convertible = requires(T& tValue) {
266 { MemberOverrideCoverter<K, N>::fromJson(tValue, QJsonValue()) } -> std::same_as<Qx::JsonError>;
267 { MemberOverrideCoverter<K, N>::toJson(tValue) } -> qjson_type;
268};
269
270template<typename Key, class Value>
271Key keygen(const Value& value) = delete;
272
273template<typename Key, class Value>
274concept json_keyable = requires(const Value& v) {
275 { keygen<Key, Value>(v) } -> std::same_as<Key>;
276};
277
278template<typename T>
281
282template<typename T>
286
287template<typename T>
290
291template<typename T>
294
295} // namespace QxJson
296
298namespace QxJsonPrivate
299{
300//-Namespace Variables---------------------------------------------------
301static inline const QString ERR_CONV_TYPE = u"JSON Error: Converting value to %1"_s;
302static inline const QString ERR_NO_KEY = u"JSON Error: Could not retrieve key '%1'."_s;
303static inline const QString ERR_PARSE_DOC = u"JSON Error: Could not parse JSON document."_s;
304static inline const QString ERR_READ_FILE = u"JSON Error: Could not read JSON file."_s;
305static inline const QString ERR_READ_DATA = u"JSON Error: Could not read JSON data."_s;
306static inline const QString ERR_WRITE_FILE = u"JSON Error: Could not write JSON file."_s;
307
308//-Structs---------------------------------------------------------------
309template<Qx::CStringLiteral MemberN, typename MemberT, class Struct>
310struct MemberMetadata
311{
312 constexpr static Qx::CStringLiteral M_NAME = MemberN;
313 typedef MemberT M_TYPE;
314 MemberT Struct::* mPtr;
315};
316
317//-Functions-------------------------------------------------------------
318template <Qx::CStringLiteral N, typename T, class S>
319constexpr MemberMetadata<N, T, S> makeMemberMetadata(T S::*memberPtr)
320{
321 return {memberPtr};
322}
323
324template<typename T> [[maybe_unused]] static inline QString typeString() = delete;
325template<typename T> [[maybe_unused]] static inline bool isType(const QJsonValue& v) = delete;
326template<typename T> [[maybe_unused]] static inline T toType(const QJsonValue& v) = delete;
327
328template<> inline QString typeString<bool>() { return u"bool"_s; };
329template<> inline QString typeString<double>() { return u"double"_s; };
330template<> inline QString typeString<QString>() { return u"string"_s; };
331template<> inline QString typeString<QJsonArray>() { return u"array"_s; };
332template<> inline QString typeString<QJsonObject>() { return u"object"_s; };
333
334template<> inline bool isType<bool>(const QJsonValue& v) { return v.isBool(); };
335template<> inline bool isType<double>(const QJsonValue& v) { return v.isDouble(); };
336template<> inline bool isType<QString>(const QJsonValue& v) { return v.isString(); };
337template<> inline bool isType<QJsonArray>(const QJsonValue& v) { return v.isArray(); };
338template<> inline bool isType<QJsonObject>(const QJsonValue& v) { return v.isObject(); };
339
340template<> inline bool toType<bool>(const QJsonValue& v) { return v.toBool(); };
341template<> inline double toType<double>(const QJsonValue& v) { return v.toDouble(); };
342template<> inline QString toType<QString>(const QJsonValue& v) { return v.toString(); };
343template<> inline QJsonArray toType<QJsonArray>(const QJsonValue& v) { return v.toArray(); };
344template<> inline QJsonObject toType<QJsonObject>(const QJsonValue& v) { return v.toObject(); };
345
346//-Functions-------------------------------------------------------------
347/* These helpers are required as a form on indirection within
348 *
349 * template<typename T>
350 * requires json_struct<T>
351 * struct Converter<T> {...}
352 *
353 * That functions makes/made use of 'if constexpr' statements that contain
354 * references to a type in their true branches that doesn't exist if the
355 * false branches are taken. Different compilers seem to discard the untaken
356 * branch of the value dependent statement at different stages as it compiled
357 * fine with MSVC and a newer version of GCC, but not clang or older GCC versions.
358 * To clarify, compilation would fail due to the type in the not-yet-discarded
359 * branch not being declared.
360 *
361 * Putting the reference of the potentially undeclared types behind these functions
362 * that always exist solves the issue.
363 *
364 * These likely can be avoided with C++23's 'if consteval'.
365 */
366template<class K>
367 requires QxJson::json_struct_inside<K>
368constexpr auto getMemberMeta()
369{
370 return K::template QxJsonMetaStructInside<K>::memberMetadata();
371}
372
373template<class K>
374 requires QxJson::json_struct_outside<K>
375constexpr auto getMemberMeta()
376{
377 return QxJson::QxJsonMetaStructOutside<K, K>::memberMetadata();
378}
379
380template<class K, typename T, Qx::CStringLiteral N>
381 requires QxJson::json_override_convertible<K, T, N>
382Qx::JsonError overrideParse(T& value, const QJsonValue& jv)
383{
384 return QxJson::MemberOverrideCoverter<K, N>::fromJson(value, jv);
385}
386
387template<class K, typename T, Qx::CStringLiteral N>
388 requires QxJson::json_override_convertible<K, T, N>
389auto overrideSerialize(const T& value)
390{
391 return QxJson::MemberOverrideCoverter<K, N>::toJson(value);
392}
393
394template<typename T>
395 requires QxJson::json_convertible<T>
396Qx::JsonError standardParse(T& value, const QJsonValue& jv)
397{
398 return QxJson::Converter<T>::fromJson(value, jv);
399}
400
401template<typename T>
402 requires QxJson::json_convertible<T>
403auto standardSerialize(const T& value)
404{
405 return QxJson::Converter<T>::toJson(value);
406}
408
409} // namespace QxJsonPrivate
410
411
412namespace QxJson
413{
414//-Default Converter Specializations-------------------------------------
416template<typename T>
417 requires qjson_type<T>
418struct Converter<T>
419{
420 static Qx::JsonError fromJson(T& value, const QJsonValue& jValue)
421 {
422 if(!QxJsonPrivate::isType<T>(jValue))
423 return Qx::JsonError(QxJsonPrivate::ERR_CONV_TYPE.arg(QxJsonPrivate::typeString<T>()), Qx::JsonError::TypeMismatch);
424
425 value = QxJsonPrivate::toType<T>(jValue);
426 return Qx::JsonError();
427 }
428
429 static T toJson(const T& value)
430 {
431 /* We could automatically box with QJsonValue here, but it's technically unnecessary as every possible
432 * type returned here is implicitly convertible to QJsonValue. Additionally, doing so would complicate
433 * the json_convertible concept as it would have to allow QJsonValue as well as the underlying
434 * types.
435 */
436 return value; // No-op
437 }
438};
439
440template<typename T>
441 requires json_struct<T>
442struct Converter<T>
443{
444 static Qx::JsonError fromJson(T& value, const QJsonValue& jValue)
445 {
446 if(!jValue.isObject())
447 return Qx::JsonError(QxJsonPrivate::ERR_CONV_TYPE.arg(QxJsonPrivate::typeString<QJsonObject>()), Qx::JsonError::TypeMismatch)
448 .withContext(QxJson::Object());
449
450 // Underlying object
451 QJsonObject jObject = jValue.toObject();
452
453 // Error tracker
454 Qx::JsonError cnvError;
455
456 // Get member metadata tuple
457 constexpr auto memberMetas = QxJsonPrivate::getMemberMeta<T>();
458
459 // "Iterate" over each tuple element via std::apply, with a fold expression
460 // utilizing && which short-circuits. This allows us to "break" the loop
461 // upon a member conversion failure as single return of false in the inner-most
462 // lambda will trigger the short-circuit.
463 std::apply([&](auto&&... memberMeta) constexpr {
464 // Fold expression
465 ([&]{
466 // Meta
467 static constexpr auto mName = std::remove_reference<decltype(memberMeta)>::type::M_NAME;
468 constexpr QLatin1StringView mKey(mName);
469 using mType = typename std::remove_reference<decltype(memberMeta)>::type::M_TYPE;
470 auto& mRef = value.*(memberMeta.mPtr);
471
472 // Get value from key
473 if(!jObject.contains(mKey))
474 {
475 if constexpr(json_optional<mType>)
476 {
477 mRef = std::nullopt;
478 return true;
479 }
480 else
481 {
482 cnvError = Qx::JsonError(QxJsonPrivate::ERR_NO_KEY.arg(mKey), Qx::JsonError::MissingKey)
483 .withContext(QxJson::Object());
484 return false;
485 }
486 }
487 QJsonValue mValue = jObject.value(mKey);
488
489 // Convert value
490 if constexpr(json_override_convertible<T, mType, mName>)
491 cnvError = QxJsonPrivate::overrideParse<T, mType, mName>(mRef, mValue);
492 else
493 cnvError = QxJsonPrivate::standardParse<mType>(mRef, mValue);
494
495 cnvError.withContext(QxJson::ObjectKey(mKey)).withContext(QxJson::Object());
496 return !cnvError.isValid();
497 }() && ...);
498 }, memberMetas);
499
500 return cnvError;
501 }
502
503 static QJsonObject toJson(const T& value)
504 {
505 // Object to fill
506 QJsonObject jObject;
507
508 // Get member metadata tuple
509 constexpr auto memberMetas = QxJsonPrivate::getMemberMeta<T>();
510
511 // "Iterate" over each tuple element via std::apply and a fold expression
512 std::apply([&](auto&&... memberMeta) constexpr {
513 // Fold expression
514 ([&]{
515 // Meta
516 static constexpr auto mName = std::remove_reference<decltype(memberMeta)>::type::M_NAME;
517 constexpr QLatin1StringView mKey(mName);
518 using mType = typename std::remove_reference<decltype(memberMeta)>::type::M_TYPE;
519 auto& mRef = value.*(memberMeta.mPtr);
520
521 // Ignore if empty optional
522 if constexpr(json_optional<mType>)
523 {
524 if(!mRef)
525 return;
526 }
527
528 // Convert value and insert
529 if constexpr(json_override_convertible<T, mType, mName>)
530 jObject.insert(mKey, QxJsonPrivate::overrideSerialize<T, mType, mName>(mRef));
531 else
532 jObject.insert(mKey, QxJsonPrivate::standardSerialize<mType>(mRef));
533 }(), ...);
534 }, memberMetas);
535
536 return jObject;
537 }
538};
539
540template<typename T>
541 requires json_collective<T>
542struct Converter<T>
543{
544 using E = typename T::value_type;
545
546 static Qx::JsonError fromJson(T& value, const QJsonValue& jValue)
547 {
548 // Reset buffer
549 value.clear();
550
551 if(!jValue.isArray())
552 {
553 return Qx::JsonError(QxJsonPrivate::ERR_CONV_TYPE.arg(QxJsonPrivate::typeString<QJsonArray>()), Qx::JsonError::TypeMismatch)
554 .withContext(QxJson::Array());
555 }
556
557 // Underlying Array
558 QJsonArray jArray = jValue.toArray();
559
560 // Error tracking
561 Qx::JsonError cnvError;
562
563 // Convert all
564 for(auto i = 0; i < jArray.count(); ++i)
565 {
566 E converted;
567 if(cnvError = Converter<E>::fromJson(converted, jArray[i]); cnvError.isValid())
568 {
569 value.clear();
570 return cnvError.withContext(QxJson::ArrayElement(i)).withContext(QxJson::Array());
571 }
572
573 value << converted;
574 }
575
576 return Qx::JsonError();
577 }
578
579 static QJsonArray toJson(const T& value)
580 {
581 // Array to fill
582 QJsonArray jArray;
583
584 // Convert all
585 for(const E& e : value)
586 {
587 // Ignore if empty optional
588 if constexpr(json_optional<E>)
589 {
590 if(!e)
591 continue;
592 }
593
594 jArray.append(Converter<E>::toJson(e));
595 }
596
597 return jArray;
598 }
599};
600
601template<typename T>
602 requires json_associative<T>
603struct Converter<T>
604{
605 using K = typename T::key_type;
606 using V = typename T::mapped_type;
607
608 static Qx::JsonError fromJson(T& value, const QJsonValue& jValue)
609 {
610 // Reset buffer
611 value.clear();
612
613 if(!jValue.isArray())
614 {
615 return Qx::JsonError(QxJsonPrivate::ERR_CONV_TYPE.arg(QxJsonPrivate::typeString<QJsonArray>()), Qx::JsonError::TypeMismatch)
616 .withContext(QxJson::Array());
617 }
618 // Underlying Array
619 QJsonArray jArray = jValue.toArray();
620
621 // Error tracking
622 Qx::JsonError cnvError;
623
624 // Convert all
625 for(auto i = 0; i < jArray.count(); ++i)
626 {
627 V converted;
628 if(cnvError = Converter<V>::fromJson(converted, jArray[i]); cnvError.isValid())
629 {
630 value.clear();
631 return cnvError.withContext(QxJson::ArrayElement(i)).withContext(QxJson::Array());
632 }
633
634 value.insert(keygen<K, V>(converted), converted);
635 }
636
637 return Qx::JsonError();
638 }
639
640 static QJsonArray toJson(const T& value)
641 {
642 // Array to fill
643 QJsonArray jArray;
644
645 // Convert all
646 for(const V& v : value)
647 {
648 // Ignore if empty optional
649 if constexpr(json_optional<V>)
650 {
651 if(!v)
652 continue;
653 }
654
655 jArray.append(Converter<V>::toJson(v));
656 }
657
658 return jArray;
659 }
660};
661
662template<typename T>
663 requires json_optional<T>
664struct Converter<T>
665{
666 using O = typename T::value_type;
667
668 static Qx::JsonError fromJson(T& value, const QJsonValue& jValue)
669 {
670 O opt;
671 Qx::JsonError je = Converter<O>::fromJson(opt, jValue);
672
673 if(!je.isValid())
674 value = std::move(opt);
675
676 return je;
677 }
678
679 static auto toJson(const T& value)
680 {
681 Q_ASSERT(value); // Optional must have value if this is reached
682 return Converter<O>::toJson(*value);
683 }
684};
685
686template<typename T>
687 requires std::integral<T> && (!std::same_as<T, bool>)
688struct Converter<T>
689{
690 static Qx::JsonError fromJson(T& value, const QJsonValue& jValue)
691 {
692 if(!jValue.isDouble())
693 return Qx::JsonError(QxJsonPrivate::ERR_CONV_TYPE.arg(QxJsonPrivate::typeString<double>()), Qx::JsonError::TypeMismatch);
694
695 value = static_cast<T>(jValue.toDouble());
696 return Qx::JsonError();
697 }
698
699 static double toJson(const T& value)
700 {
701 return static_cast<double>(value);
702 }
703};
705
706} // namespace QxJson
707
708namespace Qx
709{
710
711//-Concepts (cont.)-------------------------------------------------------------------------------------------------
712template<typename T>
715
716//-Classes---------------------------------------------------------------------------------------------------------
717class QX_CORE_EXPORT QJsonParseErrorAdapter: public Qx::AbstractError<"QJsonParseError", 500>
718{
719 //-Class Variables-------------------------------------------------------------------------------------
720private:
721 static inline const QString OFFSET_STR = u"Position: %1."_s;
722
723 //-Instance Variables-------------------------------------------------------------------------------------
724private:
725 const QJsonParseError& mErrorRef;
726
727 //-Constructor---------------------------------------------------------------------------------------------
728public:
729 QJsonParseErrorAdapter(const QJsonParseError& e);
732
733 //-Instance Functions-------------------------------------------------------------------------------------
734private:
735 quint32 deriveValue() const override;
736 QString derivePrimary() const override;
737 QString deriveSecondary() const override;
738};
739
740//-Functions-------------------------------------------------------------------------------------------------------
741template<typename T>
743JsonError parseJson(T& parsed, const QJsonObject& obj)
744{
745 // Use QJsonValue for semi-type erasure
746 QJsonValue objAsValue(obj);
747
748 return QxJson::Converter<T>::fromJson(parsed, objAsValue);
749}
750
751template<typename T>
753void serializeJson(QJsonObject& serialized, const T& struc)
754{
755 serialized = QxJson::Converter<T>::toJson(struc);
756}
757
758template<typename T>
760JsonError parseJson(T& parsed, const QJsonArray& array)
761{
762 // Use QJsonValue for semi-type erasure
763 QJsonValue arrayAsValue(array);
764
765 return QxJson::Converter<T>::fromJson(parsed, arrayAsValue);
766}
767
768template<typename T>
770void serializeJson(QJsonArray& serialized, const T& container)
771{
772 serialized = QxJson::Converter<T>::toJson(container);
773}
774
775template<typename T>
776 requires json_root<T>
777JsonError parseJson(T& parsed, const QJsonDocument& doc)
778{
779 if(doc.isEmpty())
780 return JsonError(QxJsonPrivate::ERR_PARSE_DOC, JsonError::EmptyDoc).withContext(QxJson::Document());
781
782 if constexpr(QxJson::json_containing<T>)
783 {
784 if(!doc.isArray())
785 return JsonError(QxJsonPrivate::ERR_PARSE_DOC, JsonError::TypeMismatch).withContext(QxJson::Document());
786
787 return parseJson(parsed, doc.array()).withContext(QxJson::Document());
788 }
789 else
790 {
791 if(!doc.isObject())
792 return JsonError(QxJsonPrivate::ERR_PARSE_DOC, JsonError::TypeMismatch).withContext(QxJson::Document());
793
794 return parseJson(parsed, doc.object()).withContext(QxJson::Document());
795 }
796}
797
798template<typename T>
799 requires json_root<T>
800void serializeJson(QJsonDocument& serialized, const T& root)
801{
802 serialized = QJsonDocument(QxJson::Converter<T>::toJson(root));
803}
804
805template<typename T>
806 requires json_root<T>
807JsonError parseJson(T& parsed, const QByteArray& data)
808{
809 // Check for no data
810 if(data.isEmpty())
811 return JsonError(QxJsonPrivate::ERR_READ_DATA, JsonError::EmptyDoc).withContext(QxJson::Data());
812
813 // Basic parse
814 QJsonParseError jpe;
815 QJsonDocument jd = QJsonDocument::fromJson(data, &jpe);
816
817 if(jpe.error != jpe.NoError)
818 return JsonError(QxJsonPrivate::ERR_READ_DATA, JsonError::InvalidData).withContext(QxJson::Data(jpe.errorString()));
819
820 // True parse
821 return parseJson(parsed, jd).withContext(QxJson::Data());
822}
823
824template<typename T>
825 requires json_root<T>
826void serializeJson(QByteArray& serialized, const T& root, QJsonDocument::JsonFormat fmt = QJsonDocument::Indented)
827{
828 // Ensure buffer is clear
829 serialized.clear();
830
831 // Create document
832 QJsonDocument jd;
833 serializeJson(jd, root);
834
835 // Write data
836 serialized = jd.toJson(fmt);
837}
838
839template<typename T>
840 requires json_root<T>
841JsonError parseJson(T& parsed, QFile& file)
842{
843 // NOTE: Don't utilize the QByteArray "data" overload here as we would lose the better error info
844 if(!file.exists())
845 return JsonError(QxJsonPrivate::ERR_READ_FILE, JsonError::MissingFile).withContext(QxJson::File(file.fileName()));
846
847 // Close and re-open file, if open, to ensure correct mode and start of file
848 if(file.isOpen())
849 file.close();
850
851 if(!file.open(QIODevice::ReadOnly))
852 return JsonError(QxJsonPrivate::ERR_READ_FILE, JsonError::InaccessibleFile).withContext(QxJson::File(file.fileName(), file.errorString()));
853
854 // Close file when finished
855 QScopeGuard fileGuard([&file]{ file.close(); });
856
857 // Read data
858 QByteArray jsonData = file.readAll();
859 if(jsonData.isEmpty())
860 {
861 if(file.error() != QFileDevice::NoError)
862 return JsonError(QxJsonPrivate::ERR_READ_FILE, JsonError::FileReadError).withContext(QxJson::File(file.fileName(), file.errorString()));
863 else
864 return JsonError(QxJsonPrivate::ERR_READ_FILE, JsonError::EmptyDoc).withContext(QxJson::File(file.fileName()));
865 }
866
867 // Basic parse
868 QJsonParseError jpe;
869 QJsonDocument jd = QJsonDocument::fromJson(jsonData, &jpe);
870
871 if(jpe.error != jpe.NoError)
872 return JsonError(QxJsonPrivate::ERR_READ_FILE, JsonError::FileReadError).withContext(QxJson::File(file.fileName(), jpe.errorString()));
873
874 // True parse
875 return parseJson(parsed, jd).withContext(QxJson::File(file.fileName()));
876}
877
878template<typename T>
879 requires json_root<T>
880JsonError serializeJson(QFile& serialized, const T& root, QJsonDocument::JsonFormat fmt = QJsonDocument::Indented)
881{
882 // Close and re-open file, if open, to ensure correct mode and start of file
883 if(serialized.isOpen())
884 serialized.close();
885
886 if(!serialized.open(QIODevice::Truncate | QIODevice::WriteOnly))
887 return JsonError(QxJsonPrivate::ERR_WRITE_FILE, JsonError::InaccessibleFile).withContext(QxJson::File(serialized.fileName(), serialized.errorString()));
888
889 // Close file when finished
890 QScopeGuard fileGuard([&serialized]{ serialized.close(); });
891
892 // Serialize
893 QByteArray jsonData;
894 serializeJson(jsonData, root, fmt);
895 if(serialized.write(jsonData) != jsonData.size())
896 return JsonError(QxJsonPrivate::ERR_WRITE_FILE, JsonError::FileWriteError).withContext(QxJson::File(serialized.fileName(), serialized.errorString()));
897
898 return JsonError();
899}
900
901template<typename T>
902 requires json_root<T>
903JsonError parseJson(T& parsed, const QString& filePath)
904{
905 QFile file(filePath);
906
907 return parseJson(parsed, file);
908}
909
910template<typename T>
911 requires json_root<T>
912JsonError serializeJson(const QString& serializedPath, const T& root, QJsonDocument::JsonFormat fmt = QJsonDocument::Indented)
913{
914 QFile file(serializedPath);
915
916 return serializeJson(file, root, fmt);
917}
918
919QX_CORE_EXPORT QList<QJsonValue> findAllValues(const QJsonValue& rootValue, QStringView key);
920QX_CORE_EXPORT QString asString(const QJsonValue& value);
921
922} // namespace Qx
924
925#endif // QX_JSON_H
QString string() const
Definition qx-json.cpp:629
ArrayElement(uint element)
Definition qx-json.cpp:624
Array()
Definition qx-json.cpp:604
QString string() const
Definition qx-json.cpp:609
The document class represents a JSON data node for use in error contexts.
Definition qx-json.h:100
QString string() const
Definition qx-json.cpp:522
Data(const QString &dataError={})
Definition qx-json.cpp:515
The document class represents a JSON document node for use in error contexts.
Definition qx-json.h:111
QString string() const
Definition qx-json.cpp:549
Document(const QString &name={})
Definition qx-json.cpp:544
The file class represents a JSON file node for use in error contexts.
Definition qx-json.h:86
File(const QString &filename, const QString &fileError={})
Definition qx-json.cpp:466
QString string() const
Definition qx-json.cpp:492
ObjectKey(const QString &name)
Definition qx-json.cpp:584
QString string() const
Definition qx-json.cpp:589
QString string() const
Definition qx-json.cpp:569
Object()
Definition qx-json.cpp:564
The AbstractError template class completes the Error interface and acts as the base class from which ...
Definition qx-abstracterror.h:88
The JsonError class is used to report errors related to JSON manipulation.
Definition qx-json.h:167
Form
Definition qx-json.h:171
@ MissingFile
Definition qx-json.h:178
@ NoError
Definition qx-json.h:172
@ EmptyDoc
Definition qx-json.h:175
@ InaccessibleFile
Definition qx-json.h:179
@ FileWriteError
Definition qx-json.h:181
@ FileReadError
Definition qx-json.h:180
@ InvalidValue
Definition qx-json.h:176
@ TypeMismatch
Definition qx-json.h:174
@ MissingKey
Definition qx-json.h:173
JsonError & withContext(const QxJson::ContextNode &node)
Definition qx-json.cpp:220
JsonError()
Definition qx-json.cpp:156
bool isValid() const
Definition qx-json.cpp:189
Allows QJsonParseError to be used via the Qx::Error interface.
Definition qx-json.h:718
QJsonParseErrorAdapter(const QJsonParseError &e)
Definition qx-json.cpp:249
Specifies that a type is an associative container, the value type of which is convertible,...
Definition qx-json.h:283
Specifies that a type is a non-associative container, the value type of which is convertible to/from ...
Definition qx-json.h:279
Specifies that a type is a container, and abides by the other corresponding restrictions for that kin...
Definition qx-json.h:288
Specifies that a type is generally convertible to/from JSON.
Definition qx-json.h:259
Specifies that a type has a known method for creating a corresponding key.
Definition qx-json.h:274
Specifies that a type is a specialization of std::optional that manages values of a JSON convertible ...
Definition qx-json.h:292
Specifies that a type has override conversions for changing to/from JSON.
Definition qx-json.h:265
Specifies that a type is a JSON-tied struct registered with QX_JSON_STRUCT().
Definition qx-json.h:245
Specifies that a type is a JSON-tied struct registered with QX_JSON_STRUCT_OUTSIDE().
Definition qx-json.h:250
Specifies that a type is a JSON-tied struct.
Definition qx-json.h:255
Specifies that a type is one of the fundamental JSON types within Qt's JSON system.
Definition qx-json.h:242
Specifies that a type is one of several types.
Definition qx-concepts.h:509
Specifies that a type is a valid analogue for a JSON document root element.
Definition qx-json.h:713
Specifies that a type is a Qt-based associative container type.
Definition qx-concepts.h:520
Specifies that a type is a Qt-based collection type.
Definition qx-concepts.h:524
Specifies that a type is a specialization of a template.
Definition qx-concepts.h:513
The QxJson namespace encapsulates the user-extensible implementation of Qx's JSON parsing facilities.
Definition qx-json.cpp:440
std::variant< File, Data, Document, Object, ObjectKey, Array, ArrayElement > ContextNode
Definition qx-json.h:159
Key keygen(const Value &value)=delete
The keygen template function acts as an interface through which the derivation of a key for a given t...
The Qx namespace is the main namespace through which all non-global functionality of the Qx library i...
Definition qx-abstracterror.cpp:13
void serializeJson(QJsonObject &serialized, const T &struc)
Definition qx-json.h:753
QString asString(const QJsonValue &value)
Definition qx-json.cpp:414
JsonError parseJson(T &parsed, const QJsonObject &obj)
Definition qx-json.h:743
QList< QJsonValue > findAllValues(const QJsonValue &rootValue, QStringView key)
Definition qx-json.cpp:402
The qx-abstracterror.h header file provides access to the base class from which custom error types sh...
The qx-concepts header file provides a library of general purpose concepts as an extension of the sta...
The qx-error.h header file provides access to the Error interface.
#define QX_DECLARE_ERROR_ADAPTATION(Adaptable, Adapter)
Definition qx-error.h:164
The qx-concepts header file provides a set of various object-like and function-like macros that are d...
The Converter template struct acts as an interface that carries details on how to parse/serialize JSO...
Definition qx-json.h:231
CStringLiteral acts like a typedef/alias for a StringLiteral that uses char as its storage type.
Definition qx-stringliteral.h:194