// Copyright (c) 2022 Robin Davies // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #pragma once #include #include #include #include #include #include #include #include "HtmlHelper.hpp" #include #include #include "PiPedalException.hpp" #include #define DECLARE_JSON_MAP(CLASSNAME) \ static json_map::storage_type jmap #define JSON_MAP_BEGIN(CLASSNAME) \ json_map::storage_type CLASSNAME::jmap { { #define JSON_MAP_REFERENCE(class,name) \ json_map::reference(#name,&class::name##_), #define JSON_MAP_REFERENCE_CONDITIONAL(class,name, conditionFn) \ json_map::conditional_reference(#name,&class::name##_,conditionFn) , #define JSON_MAP_END() \ }}; namespace pipedal { // a function pointer of type bool (*)(const CLASS*,const MEMBER_TYPE&value) (because pre-C++ 20 templated function pointers are *hard*) template class JsonConditionFunction { public: using Pointer = bool (*)(const CLASS*self,const MEMBER_TYPE&value); }; //---------------------------------------------------------------------------------- class json_reader; class json_writer; class JsonWritable { public: virtual void write_members(json_writer&writer) const = 0; }; template class json_member_reference_base { private: const char*name_; protected: json_member_reference_base(const char*name) :name_(name) { } public: virtual ~json_member_reference_base() {} const char*name() { return this->name_; } virtual bool canWrite(const CLASS *self) { return true; } virtual void read_value(json_reader&reader, CLASS*self) = 0; virtual void write_value(json_writer&writer, const CLASS*self) = 0; }; template class json_member_reference: public json_member_reference_base { public: virtual ~json_member_reference() {} json_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer) : json_member_reference_base(name) , member_pointer(member_pointer) { } MEMBER_TYPE CLASS::*member_pointer; void read_value(json_reader&reader, CLASS*self); void write_value(json_writer&writer, const CLASS*self); }; template class json_conditional_member_reference: public json_member_reference_base { public: virtual ~json_conditional_member_reference() {} json_conditional_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer,typename JsonConditionFunction::Pointer condition) : json_member_reference_base(name) , member_pointer(member_pointer) , condition_(condition) { } MEMBER_TYPE CLASS::*member_pointer; typename JsonConditionFunction::Pointer condition_; virtual bool canWrite(const CLASS *self); void read_value(json_reader&reader, CLASS*self); void write_value(json_writer&writer, const CLASS*self); }; template class json_enum_converter { public: virtual CLASS fromString(const std::string&value) const = 0; virtual const std::string& toString(CLASS value) const = 0; }; template class json_enum_member_reference: public json_member_reference_base { public: json_enum_member_reference(const char*name, MEMBER_TYPE CLASS::*member_pointer, const json_enum_converter*converter) : json_member_reference_base(name) , member_pointer(member_pointer) , converter_(converter) { } MEMBER_TYPE CLASS::*member_pointer; const json_enum_converter *converter_; void read_value(json_reader&reader, CLASS*self); void write_value(json_writer&writer, const CLASS*self); }; //---------------------------------------------------------------------------------- class json_map_base { }; template class json_map_impl: public json_map_base { private: std::vector* > members_; json_map_impl(const json_map_impl&) { } // disable copy constructor. public: using members_t = std::vector*>; json_map_impl(const members_t &members) : members_(members) { } json_map_impl(members_t &&members) : members_(std::forward(members)) { } virtual ~json_map_impl() { for (auto member: members_) { delete member; } members_.resize(0); } void read_property(json_reader* reader,const char*memberName,CLASS *pObject); void write_members(json_writer* writer, const CLASS*pObject); }; class json_map { public: template class storage_type : public json_map_impl { public: using members_t = std::vector*>; storage_type(const members_t &members) : json_map_impl(members) { } storage_type(members_t &&members) : json_map_impl(std::forward(members)) { } }; template static json_member_reference * reference(const char*name,MEMBER_TYPE CLASS::*member_pointer) { return new json_member_reference(name,member_pointer); }; template static json_enum_member_reference * enum_reference( const char*name, MEMBER_TYPE CLASS::*member_pointer, const ENUM_CONVERTER*converter) { return new json_enum_member_reference(name,member_pointer,converter); }; template static json_conditional_member_reference * conditional_reference(const char*name,MEMBER_TYPE CLASS::*member_pointer,typename JsonConditionFunction::Pointer condition ) { return new json_conditional_member_reference(name,member_pointer,condition); }; }; //---------------------------------------------------------------------------------- template class HasJsonPropertyMap { template static std::true_type test (decltype(TYPE::jmap) *) { return std::true_type();}; template static std::false_type test (...); public: static constexpr bool value = decltype(test(nullptr))::value; }; //---------------------------------------------------------------------------------- class json_writer; class json_writer { public: const char*CRLF; private: bool allowNaN_ = true; std::ostream &os; int indent_level; bool compressed; const int TAB_SIZE = 2; const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U; const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U; const uint16_t UTF16_SURROGATE_MASK = 0x3FFFU; static const uint32_t UTF8_ONE_BYTE_MASK = 0x80; static const uint32_t UTF8_ONE_BYTE_BITS = 0; static const uint32_t UTF8_TWO_BYTES_MASK = 0xE0; static const uint32_t UTF8_TWO_BYTES_BITS = 0xC0; static const uint32_t UTF8_THREE_BYTES_MASK = 0xF0; static const uint32_t UTF8_THREE_BYTES_BITS = 0xE0; static const uint32_t UTF8_FOUR_BYTES_MASK = 0xF8; static const uint32_t UTF8_FOUR_BYTES_BITS = 0xF0; static const uint32_t UTF8_CONTINUATION_MASK = 0xC0; static const uint32_t UTF8_CONTINUATION_BITS = 0x80; public: static std::string encode_string(const std::string &text) { std::stringstream s; json_writer writer(s); writer.write(text); return s.str(); } void write_raw(const char*text) { os << text; } using string_view = boost::string_view; json_writer(std::ostream &os, bool compressed = true, bool allowNaN = true) : os(os) , compressed(compressed) , allowNaN_(allowNaN) , indent_level(0) { this->CRLF = compressed? "" : "\r\n"; } bool allowNaN() const { return allowNaN_; } void allowNaN(bool allow) { allowNaN_ = allow; } void write(long long value) { os << value; } void write(unsigned long long value) { os << value; } void write(long value) { os << value; } void write(unsigned long value) { os << value; } void write(int value) { os << value; } void write(unsigned int value) { os << value; } private: static void throw_encoding_error(); static uint32_t continuation_byte(std::string_view::iterator &p, std::string_view::const_iterator end); void write_utf16_char(uint16_t uc); public: void indent(); std::ostream & output_stream() { return os; } void write(bool value) { os << (value? "true": "false"); } void write( string_view v, bool enforceValidUtf8 // throw errors for illegal (non-minimal) UTF-8 sequences. ); void write(string_view v) { write(v,true); } void write(const char*p) { write(string_view(p)); } void write(const std::string&s) { write(string_view(s.c_str())); } void write(float f) { if (allowNaN_ && (std::isnan(f) || std::isinf(f))) { os << "NaN"; } else { os << std::setprecision(std::numeric_limits::max_digits10) << f; // round-trip format } } void write(double f) { if (allowNaN_ && (std::isnan(f) || std::isinf(f))) { os << "NaN"; } else { os << std::setprecision(std::numeric_limits::max_digits10) << f; // round-trip format } } template void write(const std::vector &value) { if (std::is_fundamental() || std::is_assignable() || value.size() == 0) { // simple types: all on same line. os << "[ "; if (value.size() >= 1) { write(value[0]); } for (size_t i = 1; i < value.size(); ++i) { os << ","; write(value[i]); } os << "]"; } else { // complex types: one line per entry. os << "[" << CRLF; indent_level += TAB_SIZE; bool first = true; for (size_t i = 0; i < value.size(); ++i) { if (!first) { os << ',' << CRLF; } first = false; indent(); write(value[i]); } indent_level -= TAB_SIZE; os << CRLF; indent(); os << "]"; } } template< class Category, class T, class Distance = std::ptrdiff_t, class Pointer = T*, class Reference = T& > void write(std::iterator &it) { start_array(); bool first = true; for( Reference ref: it) { if (!first) { os << ", "; os << CRLF; } indent(); os << ref; } end_array(); } void write_json_member(const char*name, const char*json_text) { write(name); os << ": "; os << json_text; } template void write_member(const char*name,const T&value) { write(name); os << ": "; write(value); } void start_object(); void end_object(); void start_array(); void end_array(); /* void write(const json_writable *obj) { if (obj == nullptr) { os << "null"; } else { start_object(); obj->write(*this); end_object(); } } void write(const json_writable &obj) { start_object(); obj.write(*this); end_object(); } */ private: template json_map::storage_type& get_object_map() { //static_assert(HasJsonPropertyMap::value,"Type does not have a Json propery map name jmap."); // Type T must have public static propery jmap. return T::jmap; } public: void write(const JsonWritable*pWritable) { start_object(); pWritable->write_members(*this); end_object(); } template void write(const std::shared_ptr &obj) { write(obj.get()); } template void write(const std::weak_ptr &obj) { auto p = obj.lock(); write(p.get()); } template void write(const std::unique_ptr &obj) { write(obj.get()); } template void write(const T*obj) { static_assert(HasJsonPropertyMap::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); if (obj == nullptr) { write_raw("null"); } else { write(*obj); } } template void write(T*obj) { static_assert(HasJsonPropertyMap::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); if (obj == nullptr) { write_raw("null"); } else { write(*obj); } } template void write(const T&obj) { static_assert(HasJsonPropertyMap::value,"Json serialization type not supported. (Json object classes must have a Json property map named jmap)."); auto &map = get_object_map(); start_object(); map.write_members(this,&obj); end_object(); } template void write(const std::map &map) { start_object(); bool firstTime = true; for (const auto&v : map) { if (!firstTime) { write_raw(","); write_raw(CRLF); } indent(); firstTime = false; write_member(v.first.c_str(),v.second); } end_object(); } }; class json_reader { private: std::istream &is_; const uint16_t UTF16_SURROGATE_1_BASE = 0xD800U; const uint16_t UTF16_SURROGATE_2_BASE = 0xDC00U; const uint16_t UTF16_SURROGATE_MASK = 0x3FFU; bool allowNaN_ = true; public: json_reader(std::istream &input, bool allowNaN = true) : is_(input) { this->allowNaN_ = allowNaN; } bool allowNaN() const { return allowNaN_;} void allowNaN(bool allow) { allowNaN_ = allow; } private: void throw_format_error(const char*error); void throw_format_error() { throw_format_error("Invalid file format"); } inline bool is_whitespace(char c) { switch (c) { case 0x20: case 0x0A: case 0x0D: case 0x09: return true; default: return false; } } char get() { int ic = is_.get(); if (ic == -1) throw_format_error("Unexpected end of file"); return (char)ic; } void skip_whitespace(); void read_object_start(); uint16_t read_hex(); uint16_t read_u_escape(); template json_map::storage_type& get_object_map() { // Type T must have public static propery map. return T::jmap; } public: void consume(char expected); void consumeToken(const char*expectedToken, const char*errorMessage); int peek() { skip_whitespace(); return is_.peek(); } public: std::string read_string(); public: bool is_complete(); template void read(T*pObject) { char c; consume('{'); auto &map = get_object_map(); while (true) { c = peek(); if (c == '}') { c = get(); break; } std::string memberName = read_string(); consume(':'); skip_whitespace(); map.read_property(this,memberName.c_str(),pObject); skip_whitespace(); if (is_.peek() == ',') { c = get(); } } } template void read(T**pObject) { int c = peek(); if (c != '{') { if (c == 'n') { consumeToken("null", "Expecting '{' or 'null'."); *pObject = nullptr; return; } } *pObject = new T(); read(*pObject); } template void read(std::map *pMap) { char c; consume('{'); while (true) { c = peek(); if (c == '}') { c = get(); break; } std::string key = read_string(); consume(':'); skip_whitespace(); U u; this->read(&u); (*pMap)[key] = u; skip_whitespace(); if (peek() == ',') { c = get(); } } } template void read(std::unique_ptr *pUniquePtr) { std::unique_ptr p = std::make_unique(); read(p.get()); *pUniquePtr = std::move(p); } template void read(std::shared_ptr *pUniquePtr) { std::shared_ptr p = std::make_shared(); read(p.get()); (*pUniquePtr) = std::move(p); } template void read(std::weak_ptr *pUniquePtr) { throw std::domain_error("Can't read std::weak_ptr"); } private: void skip_string(); void skip_number(); void skip_array(); void skip_object(); std::string readToken(); bool read_boolean(); void read_null(); void skip_token(); public: void skip_property(); void read(std::string*value) { skip_whitespace(); *value = read_string(); } void read(bool *value) { skip_whitespace(); *value = read_boolean(); } void read(int *value) { skip_whitespace(); is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(long*value) { is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(long long*value) { skip_whitespace(); is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(unsigned int*value) { is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(unsigned long*value) { is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(unsigned long long*value) { skip_whitespace(); is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(float*value) { skip_whitespace(); if (allowNaN_) { if (peek() == 'N') { consumeToken("NaN","Expecting a number."); *value = std::nanf(""); return; } } is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } void read(double*value) { skip_whitespace(); if (allowNaN_) { if (peek() == 'N') { consumeToken("NaN","Expecting a number."); *value = std::nan(""); return; } } is_ >> *value; if (is_.fail()) throw PiPedalException("Invalid format."); } template void read(std::vector*value) { char c; std::vector result; consume('['); while (true) { if (peek() == ']') { c = get(); break; } T item; read(&item); result.push_back(std::move(item)); if (peek() == ',') { c = get(); } } *value = std::move(result); } }; template class HasJsonRead { template static std::true_type test (decltype(json_reader(std::cin).read((ARG*)nullptr))*v) { return std::true_type();}; template static std::false_type test (...); public: static constexpr bool value = decltype(test(nullptr))::value; }; template void json_member_reference::read_value(json_reader&reader, CLASS*self) { static_assert(HasJsonRead::value,"Type not supported for Json serialization."); MEMBER_TYPE *ref = &((*self).*member_pointer); reader.read(ref); } template void json_member_reference::write_value(json_writer&writer, const CLASS*self) { const MEMBER_TYPE &ref = (*self).*member_pointer; writer.write_member(this->name(),ref); } template void json_enum_member_reference::read_value(json_reader&reader, CLASS*self) { MEMBER_TYPE *ref = &((*self).*member_pointer); std::string val = reader.read_string(); *ref = converter_->fromString(val); } template void json_enum_member_reference::write_value(json_writer&writer, const CLASS*self) { const MEMBER_TYPE &ref = (*self).*member_pointer; std::string val = converter_->toString(ref); writer.write_member(this->name(),val); } template void json_map_impl::read_property(json_reader* reader,const char*memberName,CLASS *pObject) { for (json_member_reference_base*member: members_) { if (strcmp(member->name(),memberName) == 0) { member->read_value(*reader,pObject); return; } } reader->skip_property(); } template void json_conditional_member_reference::read_value(json_reader&reader, CLASS*self) { static_assert(HasJsonRead::value,"Type not supported for Json serialization."); MEMBER_TYPE *ref = &((*self).*member_pointer); reader.read(ref); } template bool json_conditional_member_reference::canWrite(const CLASS*self) { const MEMBER_TYPE &ref = (*self).*member_pointer; return this->condition_(self,ref); } template void json_conditional_member_reference::write_value(json_writer&writer, const CLASS*self) { const MEMBER_TYPE &ref = (*self).*member_pointer; writer.write_member(this->name(),ref); } template void json_map_impl::write_members(json_writer* writer, const CLASS*pObject) { bool first = true; for (json_member_reference_base*member: members_) { if (member->canWrite(pObject)) { if (!first) { writer->output_stream() << ','; writer->output_stream() << writer->CRLF; } first = false; writer->indent(); member->write_value(*writer,pObject); } } } } // namespace pipedal