From ce5c689a5f9e544d724b2a41fda2f752ccc4a4ae Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Fri, 7 May 2021 20:50:48 +0100 Subject: Initial commit where writing objects back out to JSON It's not perfect, writes explicit nulls, doesn't do shared @id --- lib/jsonParse-persistence.cpp | 116 +++++++++++++++++++++++++++++++ lib/jsonParse-persistence.h | 28 ++++++++ lib/persistence.cpp | 6 ++ lib/persistence.h | 158 +++++++++++++++++++++++++++++++++++++++--- test/test-persistence.cpp | 53 +++++++++++++- 5 files changed, 351 insertions(+), 10 deletions(-) diff --git a/lib/jsonParse-persistence.cpp b/lib/jsonParse-persistence.cpp index dce9d3c..6ecfff8 100644 --- a/lib/jsonParse-persistence.cpp +++ b/lib/jsonParse-persistence.cpp @@ -1,4 +1,8 @@ #include "jsonParse-persistence.h" +#include +#include +#include +#include #include #include @@ -83,4 +87,116 @@ namespace Persistence { { return stk.top(); } + + static inline void + wrv(std::ostream & strm, char ch) + { + strm.put(ch); + }; + static inline void + wrh(std::ostream & strm, char ch) + { + using namespace std::literals; + strm << R"(\u)"sv << std::setw(4) << std::hex << (int)ch << std::setw(1); + } + static inline void + wre(std::ostream & strm, char e) + { + strm << '\\' << e; + } + template + static inline void + wre(std::ostream & strm, char) + { + wre(strm, E); + } + + using OutFunc = void (*)(std::ostream &, char); + using OutFuncs = std::array; + static constexpr OutFuncs outFuncs {[]() { + OutFuncs outFuncs; + outFuncs.fill(&wrv); + for (int x = 0; x < 0x20; x += 1) { + outFuncs[x] = &wrh; + } + outFuncs['\"'] = &wre<'"'>; + outFuncs['\\'] = &wre<'\\'>; + outFuncs['\b'] = &wre<'b'>; + outFuncs['\f'] = &wre<'f'>; + outFuncs['\n'] = &wre<'n'>; + outFuncs['\r'] = &wre<'r'>; + outFuncs['\t'] = &wre<'t'>; + return outFuncs; + }()}; + + JsonWritePersistence::JsonWritePersistence(std::ostream & s) : strm {s} + { + strm << std::boolalpha // for Boolean + << std::defaultfloat // for Number + << std::setfill('0'); // for String \uNNNN + } + + void + JsonWritePersistence::beginObject() const + { + strm << '{'; + } + + void + JsonWritePersistence::beginArray() const + { + strm << '['; + } + + void + JsonWritePersistence::pushValue(bool value) const + { + strm << value; + } + + void + JsonWritePersistence::pushValue(float value) const + { + strm << value; + } + + void JsonWritePersistence::pushValue(std::nullptr_t) const + { + strm << "null"; + } + + void + JsonWritePersistence::pushValue(const std::string_view value) const + { + strm << '"'; + std::for_each(value.begin(), value.end(), [this](char ch) { + outFuncs[ch](strm, ch); + }); + strm << '"'; + } + + void + JsonWritePersistence::nextValue() const + { + strm << ','; + } + + void + JsonWritePersistence::pushKey(const std::string_view k) const + { + pushValue(k); + strm << ':'; + } + + void + JsonWritePersistence::endArray() const + { + strm << ']'; + } + + void + JsonWritePersistence::endObject() const + { + strm << '}'; + } } diff --git a/lib/jsonParse-persistence.h b/lib/jsonParse-persistence.h index 6449f7b..edb5305 100644 --- a/lib/jsonParse-persistence.h +++ b/lib/jsonParse-persistence.h @@ -7,6 +7,7 @@ #include #include #include +#include namespace Persistence { class JsonParsePersistence : public json::jsonParser { @@ -39,6 +40,33 @@ namespace Persistence { template inline void pushValue(T && value); inline SelectionPtr & current(); }; + + class JsonWritePersistence : public Writer { + public: + explicit JsonWritePersistence(std::ostream & s); + + template + inline void + saveState(T & t) const + { + SelectionT {t}.write(*this); + } + + protected: + void beginObject() const override; + void beginArray() const override; + void pushValue(bool value) const override; + void pushValue(float value) const override; + void pushValue(std::nullptr_t) const override; + void pushValue(const std::string_view value) const override; + void nextValue() const override; + void pushKey(const std::string_view k) const override; + void endArray() const override; + void endObject() const override; + + private: + std::ostream & strm; + }; } #endif diff --git a/lib/persistence.cpp b/lib/persistence.cpp index c4ee142..4f1be8f 100644 --- a/lib/persistence.cpp +++ b/lib/persistence.cpp @@ -78,5 +78,11 @@ namespace Persistence { Selection::endObject(Stack &) { } + + void + Selection::write(const Writer &) const + { + throw std::logic_error("Default write op shouldn't ever get called"); + } /// LCOV_EXCL_STOP } diff --git a/lib/persistence.h b/lib/persistence.h index 519cb0e..4b72eb3 100644 --- a/lib/persistence.h +++ b/lib/persistence.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,19 @@ namespace Persistence { using SelectionPtr = std::unique_ptr; using Stack = std::stack; + struct Writer { + virtual void beginObject() const = 0; + virtual void beginArray() const = 0; + virtual void pushValue(bool value) const = 0; + virtual void pushValue(float value) const = 0; + virtual void pushValue(std::nullptr_t) const = 0; + virtual void pushValue(std::string_view value) const = 0; + virtual void nextValue() const = 0; + virtual void pushKey(std::string_view k) const = 0; + virtual void endArray() const = 0; + virtual void endObject() const = 0; + }; + struct Selection { Selection() = default; virtual ~Selection() = default; @@ -38,6 +52,8 @@ namespace Persistence { virtual void endObject(Stack &); virtual void beforeValue(Stack &); [[nodiscard]] virtual SelectionPtr select(const std::string &); + + virtual void write(const Writer &) const; }; template struct SelectionT; @@ -75,25 +91,90 @@ namespace Persistence { { std::swap(this->v, evalue); } + + void + write(const Writer & out) const override + { + out.pushValue(this->v); + } }; struct PersistenceStore { - template [[nodiscard]] inline bool persistType() const; + template [[nodiscard]] inline bool persistType(const T * const, const std::type_info & ti); + enum class NameAction { Push, HandleAndContinue, Ignore }; template [[nodiscard]] inline bool persistValue(const std::string_view key, T & value) { - if (key == name) { + const auto act {setName(key)}; + if (act != NameAction::Ignore) { sel = SelectionV::make(value); - return false; + if (act == NameAction::HandleAndContinue) { + selHandler(); + } } - return true; + return (act != NameAction::Push); } - const std::string & name; + + virtual NameAction setName(const std::string_view key) = 0; + virtual void selHandler() {}; + virtual void setType(const std::string_view) = 0; + SelectionPtr sel {}; }; + struct PersistenceSelect : public PersistenceStore { + explicit PersistenceSelect(const std::string & n) : name {n} { } + + NameAction + setName(const std::string_view key) override + { + return (key == name) ? NameAction::Push : NameAction::Ignore; + } + + void + setType(const std::string_view) override + { + } + + const std::string & name; + }; + + struct PersistenceWrite : public PersistenceStore { + explicit PersistenceWrite(const Writer & o) : out {o} { } + + NameAction + setName(const std::string_view key) override + { + if (!first) { + out.nextValue(); + } + else { + first = false; + } + out.pushKey(key); + return NameAction::HandleAndContinue; + } + + void + selHandler() override + { + this->sel->write(out); + }; + + void + setType(const std::string_view tn) override + { + out.pushKey("@typeid"); + out.pushValue(tn); + first = false; + } + + bool first {true}; + const Writer & out; + }; + template struct SelectionT> : public SelectionV> { using V = glm::vec; @@ -108,6 +189,17 @@ namespace Persistence { } glm::length_t idx {0}; + + void + write(const Writer & out) const override + { + for (glm::length_t idx = 0; idx < L; idx += 1) { + if (idx) { + out.nextValue(); + } + SelectionT {this->v[idx]}.write(out); + } + } }; using SelectionV::SelectionV; @@ -117,6 +209,14 @@ namespace Persistence { { stk.push(this->template make_s(this->v)); } + + void + write(const Writer & out) const override + { + out.beginArray(); + Members {this->v}.write(out); + out.endArray(); + } }; template struct SelectionT> : public SelectionV> { @@ -130,6 +230,17 @@ namespace Persistence { { stk.push(SelectionV::make(this->v.emplace_back())); } + + void + write(const Writer & out) const override + { + for (std::size_t idx = 0; idx < this->v.size(); idx += 1) { + if (idx) { + out.nextValue(); + } + SelectionT {this->v[idx]}.write(out); + } + } }; using SelectionV::SelectionV; @@ -139,6 +250,14 @@ namespace Persistence { { stk.push(this->template make_s(this->v)); } + + void + write(const Writer & out) const override + { + out.beginArray(); + Members {this->v}.write(out); + out.endArray(); + } }; struct Persistable { @@ -173,11 +292,14 @@ namespace Persistence { template inline bool - PersistenceStore::persistType() const + PersistenceStore::persistType(const T * const, const std::type_info & ti) { if constexpr (!std::is_abstract_v) { [[maybe_unused]] constexpr auto f = &Persistable::addFactory; } + if (typeid(std::decay_t) == ti) { + setType(Persistable::typeName()); + } return true; } @@ -240,7 +362,7 @@ namespace Persistence { } } make_default_as_needed(this->v); - PersistenceStore ps {mbr}; + PersistenceSelect ps {mbr}; if (this->v->persist(ps)) { throw std::runtime_error("cannot find member: " + mbr); } @@ -253,6 +375,15 @@ namespace Persistence { make_default_as_needed(this->v); stk.pop(); } + + void + write(const Writer & out) const override + { + out.beginObject(); + PersistenceWrite pw {out}; + this->v->persist(pw); + out.endObject(); + } }; static inline void @@ -289,6 +420,17 @@ namespace Persistence { { stk.pop(); } + + void + write(const Writer & out) const override + { + if (this->v) { + SelectionObj {this->v}.write(out); + } + else { + out.pushValue(nullptr); + } + } }; template struct SelectionT> : public SelectionPtrBase, false> { @@ -311,7 +453,7 @@ namespace Persistence { }; } -#define STORE_TYPE store.persistType>() +#define STORE_TYPE store.persistType(this, typeid(*this)) #define STORE_MEMBER(mbr) store.persistValue(#mbr, mbr) #endif diff --git a/test/test-persistence.cpp b/test/test-persistence.cpp index 72c8968..547396b 100644 --- a/test/test-persistence.cpp +++ b/test/test-persistence.cpp @@ -32,7 +32,7 @@ struct SubObject : public AbsObject { bool persist(Persistence::PersistenceStore & store) override { - return AbsObject::persist(store) && STORE_TYPE && STORE_MEMBER(sub); + return STORE_TYPE && AbsObject::persist(store) && STORE_MEMBER(sub); } void @@ -45,7 +45,7 @@ struct SubObject2 : public AbsObject { bool persist(Persistence::PersistenceStore & store) override { - return AbsObject::persist(store) && STORE_TYPE; + return STORE_TYPE && AbsObject::persist(store); } void @@ -297,3 +297,52 @@ BOOST_DATA_TEST_CASE(utf8_decode_bad, boost::unit_test::data::make to {}; + std::stringstream ss; + Persistence::JsonWritePersistence {ss}.saveState(to); + BOOST_CHECK_EQUAL(ss.str(), "null"); +} + +BOOST_AUTO_TEST_CASE(write_test_dfl) +{ + auto to = std::make_unique(); + std::stringstream ss; + Persistence::JsonWritePersistence {ss}.saveState(to); + // TODO We can omit writing explicit nulls + BOOST_CHECK_EQUAL(ss.str(), + R"({"@typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"ptr":null,"aptr":null,"vptr":[]})"); +} + +BOOST_FIXTURE_TEST_CASE(write_test_loaded, JPP) +{ + auto to = load_json>(FIXTURESDIR "json/load_object.json"); + std::stringstream ss; + Persistence::JsonWritePersistence {ss}.saveState(to); + // TODO We can omit writing explicit nulls + BOOST_CHECK_EQUAL(ss.str(), + R"({"@typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":true,"pos":[3.14,6.28,1.57],"flts":[3.14,6.28,1.57,0,-1,-3.14],"poss":[[3.14,6.28,1.57],[0,-1,-3.14]],"nest":[[["a","b"],["c","d","e"]],[["f"]],[]],"ptr":{"@typeid":"TestObject","flt":3.14,"str":"Lovely string","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"ptr":null,"aptr":null,"vptr":[]},"aptr":null,"vptr":[]})"); +} + +BOOST_FIXTURE_TEST_CASE(write_test_loaded_abs, JPP) +{ + auto to = load_json>(FIXTURESDIR "json/abs.json"); + std::stringstream ss; + Persistence::JsonWritePersistence {ss}.saveState(to); + // TODO We can omit writing explicit nulls + BOOST_CHECK_EQUAL(ss.str(), + R"({"@typeid":"TestObject","flt":0,"str":"","bl":false,"pos":[0,0,0],"flts":[],"poss":[],"nest":[],"ptr":null,"aptr":{"@typeid":"SubObject","base":"set base","sub":"set sub"},"vptr":[]})"); +} + +BOOST_FIXTURE_TEST_CASE(write_test_loaded_shared, JPP) +{ + auto to = load_json>(FIXTURESDIR "json/shared_ptr_same.json"); + std::stringstream ss; + Persistence::JsonWritePersistence {ss}.saveState(to); + // TODO We can omit writing explicit nulls + // TODO Also not really implemented, but it runs :) + // BOOST_CHECK_EQUAL(ss.str(), + // R"({"@typeid":"SharedTestObject","sptr":{"@typeid":"SubObject","@id":"someid"},"ssptr":"someid"})"); +} -- cgit v1.2.3