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