summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2021-05-07 20:50:48 +0100
committerDan Goodliffe <dan@randomdan.homeip.net>2021-11-07 16:41:37 +0000
commitce5c689a5f9e544d724b2a41fda2f752ccc4a4ae (patch)
tree33f2c53d2cb1b67f3c7024bbec2366be875b57d9
parentFixup clang, cppcheck and iwyu warnings (diff)
downloadilt-ce5c689a5f9e544d724b2a41fda2f752ccc4a4ae.tar.bz2
ilt-ce5c689a5f9e544d724b2a41fda2f752ccc4a4ae.tar.xz
ilt-ce5c689a5f9e544d724b2a41fda2f752ccc4a4ae.zip
Initial commit where writing objects back out to JSON
It's not perfect, writes explicit nulls, doesn't do shared @id
-rw-r--r--lib/jsonParse-persistence.cpp116
-rw-r--r--lib/jsonParse-persistence.h28
-rw-r--r--lib/persistence.cpp6
-rw-r--r--lib/persistence.h158
-rw-r--r--test/test-persistence.cpp53
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 <algorithm>
+#include <array>
+#include <iomanip>
+#include <ostream>
#include <type_traits>
#include <utility>
@@ -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<char E>
+ static inline void
+ wre(std::ostream & strm, char)
+ {
+ wre(strm, E);
+ }
+
+ using OutFunc = void (*)(std::ostream &, char);
+ using OutFuncs = std::array<OutFunc, 255>;
+ 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 <iosfwd>
#include <memory>
#include <string>
+#include <string_view>
namespace Persistence {
class JsonParsePersistence : public json::jsonParser {
@@ -39,6 +40,33 @@ namespace Persistence {
template<typename T> inline void pushValue(T && value);
inline SelectionPtr & current();
};
+
+ class JsonWritePersistence : public Writer {
+ public:
+ explicit JsonWritePersistence(std::ostream & s);
+
+ template<typename T>
+ inline void
+ saveState(T & t) const
+ {
+ SelectionT<T> {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 <string>
#include <string_view>
#include <type_traits>
+#include <typeinfo>
#include <utility>
#include <vector>
@@ -24,6 +25,19 @@ namespace Persistence {
using SelectionPtr = std::unique_ptr<Selection>;
using Stack = std::stack<SelectionPtr>;
+ 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<typename T> 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<typename T> [[nodiscard]] inline bool persistType() const;
+ template<typename T> [[nodiscard]] inline bool persistType(const T * const, const std::type_info & ti);
+ enum class NameAction { Push, HandleAndContinue, Ignore };
template<typename T>
[[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<T>::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<glm::length_t L, typename T, glm::qualifier Q>
struct SelectionT<glm::vec<L, T, Q>> : public SelectionV<glm::vec<L, T, Q>> {
using V = glm::vec<L, T, Q>;
@@ -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<T> {this->v[idx]}.write(out);
+ }
+ }
};
using SelectionV<V>::SelectionV;
@@ -117,6 +209,14 @@ namespace Persistence {
{
stk.push(this->template make_s<Members>(this->v));
}
+
+ void
+ write(const Writer & out) const override
+ {
+ out.beginArray();
+ Members {this->v}.write(out);
+ out.endArray();
+ }
};
template<typename T> struct SelectionT<std::vector<T>> : public SelectionV<std::vector<T>> {
@@ -130,6 +230,17 @@ namespace Persistence {
{
stk.push(SelectionV<T>::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<T> {this->v[idx]}.write(out);
+ }
+ }
};
using SelectionV<V>::SelectionV;
@@ -139,6 +250,14 @@ namespace Persistence {
{
stk.push(this->template make_s<Members>(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<typename T>
inline bool
- PersistenceStore::persistType() const
+ PersistenceStore::persistType(const T * const, const std::type_info & ti)
{
if constexpr (!std::is_abstract_v<T>) {
[[maybe_unused]] constexpr auto f = &Persistable::addFactory<T>;
}
+ if (typeid(std::decay_t<T>) == ti) {
+ setType(Persistable::typeName<T>());
+ }
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<typename T> struct SelectionT<std::unique_ptr<T>> : public SelectionPtrBase<std::unique_ptr<T>, false> {
@@ -311,7 +453,7 @@ namespace Persistence {
};
}
-#define STORE_TYPE store.persistType<std::decay_t<decltype(*this)>>()
+#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<unsigned long
std::string out;
BOOST_CHECK_THROW(json::jsonParser::appendEscape(cp, out), std::runtime_error);
}
+
+BOOST_AUTO_TEST_CASE(write_test_null)
+{
+ std::unique_ptr<TestObject> 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<TestObject>();
+ 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<std::unique_ptr<TestObject>>(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<std::unique_ptr<TestObject>>(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<std::unique_ptr<SharedTestObject>>(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"})");
+}