// From github.com/nbsdx/SimpleJSON. // Released under the DWTFYW PL // #ifndef SIMPLEJSON_HPP #define SIMPLEJSON_HPP #include #include #include #include #include #include #include #include #include #include #include #include "../chaiscript_defines.hpp" #include "quick_flat_map.hpp" namespace json { using std::enable_if; using std::initializer_list; using std::is_same; using std::is_convertible; using std::is_integral; using std::is_floating_point; class JSON { public: enum class Class { Null = 0, Object, Array, String, Floating, Integral, Boolean }; private: using Data = std::variant, std::vector, std::string, double, int64_t, bool>; struct Internal { Internal(std::nullptr_t) : d(nullptr) { } Internal() : d(nullptr) { } Internal(Class c) : d(make_type(c)) { } template Internal(T t) : d(std::move(t)) { } static Data make_type(Class c) { switch (c) { case Class::Null: return nullptr; case Class::Object: return chaiscript::utility::QuickFlatMap{}; case Class::Array: return std::vector{}; case Class::String: return std::string{}; case Class::Floating: return double{}; case Class::Integral: return int64_t{}; case Class::Boolean: return bool{}; } throw std::runtime_error("unknown type"); } void set_type(Class c) { if (type() != c) { d = make_type(c); } } Class type() const noexcept { return Class(d.index()); } template decltype(auto) visit_or(Visitor &&visitor, Or &&other) const { if (type() == Class(ClassValue)) { return visitor(std::get(ClassValue)>(d)); } else { return other(); } } template auto &get_set_type() { set_type(ClassValue); return (std::get(ClassValue)>(d)); } auto &Map() { return get_set_type(); } auto &Vector() { return get_set_type(); } auto &String() { return get_set_type(); } auto &Int() { return get_set_type(); } auto &Float() { return get_set_type(); } auto &Bool() { return get_set_type(); } auto Map() const noexcept { return std::get_if(Class::Object)>(&d); } auto Vector() const noexcept { return std::get_if(Class::Array)>(&d); } auto String() const noexcept { return std::get_if(Class::String)>(&d); } auto Int() const noexcept { return std::get_if(Class::Integral)>(&d); } auto Float() const noexcept { return std::get_if(Class::Floating)>(&d); } auto Bool() const noexcept { return std::get_if(Class::Boolean)>(&d); } Data d; }; Internal internal; public: template class JSONWrapper { Container *object = nullptr; public: JSONWrapper( Container *val ) : object( val ) {} JSONWrapper( std::nullptr_t ) {} typename Container::iterator begin() { return object ? object->begin() : typename Container::iterator(); } typename Container::iterator end() { return object ? object->end() : typename Container::iterator(); } typename Container::const_iterator begin() const { return object ? object->begin() : typename Container::iterator(); } typename Container::const_iterator end() const { return object ? object->end() : typename Container::iterator(); } }; template class JSONConstWrapper { const Container *object = nullptr; public: JSONConstWrapper( const Container *val ) : object( val ) {} JSONConstWrapper( std::nullptr_t ) {} typename Container::const_iterator begin() const noexcept { return object ? object->begin() : typename Container::const_iterator(); } typename Container::const_iterator end() const noexcept { return object ? object->end() : typename Container::const_iterator(); } }; JSON() = default; JSON( std::nullptr_t ) {} explicit JSON(Class type) : internal(type) { } JSON( initializer_list list ) : internal(Class::Object) { for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) { operator[]( i->to_string() ) = *std::next( i ); } } template explicit JSON( T b, typename enable_if::value>::type* = nullptr ) noexcept : internal( static_cast(b) ) {} template explicit JSON( T i, typename enable_if::value && !is_same::value>::type* = nullptr ) noexcept : internal( static_cast(i) ) {} template explicit JSON( T f, typename enable_if::value>::type* = nullptr ) noexcept : internal( static_cast(f) ) {} template explicit JSON( T s, typename enable_if::value>::type* = nullptr ) : internal( static_cast(s) ) {} static JSON Load( const std::string & ); JSON& operator[]( const std::string &key ) { return internal.Map().operator[]( key ); } JSON& operator[]( const size_t index ) { auto &vec = internal.Vector(); if( index >= vec.size() ) { vec.resize( index + 1 ); } return vec.operator[]( index ); } JSON &at( const std::string &key ) { return operator[]( key ); } const JSON &at( const std::string &key ) const { return internal.visit_or( [&](const auto &m)->const JSON &{ return m.at(key); }, []()->const JSON &{ throw std::range_error("Not an object, no keys"); } ); } JSON &at( size_t index ) { return operator[]( index ); } const JSON &at( size_t index ) const { return internal.visit_or( [&](const auto &m)->const JSON&{ return m.at(index); }, []()->const JSON &{ throw std::range_error("Not an array, no indexes"); } ); } auto length() const noexcept { return internal.visit_or( [&](const auto &m){ return static_cast(m.size()); }, [](){ return -1; } ); } bool has_key( const std::string &key ) const noexcept { return internal.visit_or( [&](const auto &m){ return m.count(key) != 0; }, [](){ return false; } ); } int size() const noexcept { if (auto m = internal.Map(); m != nullptr) { return static_cast(m->size()); } if (auto v = internal.Vector(); v != nullptr) { return static_cast(v->size()); } else { return -1; } } Class JSONType() const noexcept { return internal.type(); } /// Functions for getting primitives from the JSON object. bool is_null() const noexcept { return internal.type() == Class::Null; } std::string to_string() const noexcept { return internal.visit_or( [](const auto &o){ return o; }, [](){ return std::string{}; } ); } double to_float() const noexcept { return internal.visit_or( [](const auto &o){ return o; }, [](){ return double{}; } ); } int64_t to_int() const noexcept { return internal.visit_or( [](const auto &o){ return o; }, [](){ return int64_t{}; } ); } bool to_bool() const noexcept { return internal.visit_or( [](const auto &o){ return o; }, [](){ return false; } ); } JSONWrapper> object_range() { return std::get_if(Class::Object)>(&internal.d); } JSONWrapper> array_range() { return std::get_if(Class::Array)>(&internal.d); } JSONConstWrapper> object_range() const { return std::get_if(Class::Object)>(&internal.d); } JSONConstWrapper> array_range() const { return std::get_if(Class::Array)>(&internal.d); } std::string dump( long depth = 1, std::string tab = " ") const { switch( internal.type() ) { case Class::Null: return "null"; case Class::Object: { std::string pad = ""; for( long i = 0; i < depth; ++i, pad += tab ) { } std::string s = "{\n"; bool skip = true; for( auto &p : *internal.Map() ) { if( !skip ) { s += ",\n"; } s += ( pad + "\"" + p.first + "\" : " + p.second.dump( depth + 1, tab ) ); skip = false; } s += ( "\n" + pad.erase( 0, 2 ) + "}" ) ; return s; } case Class::Array: { std::string s = "["; bool skip = true; for( auto &p : *internal.Vector() ) { if( !skip ) { s += ", "; } s += p.dump( depth + 1, tab ); skip = false; } s += "]"; return s; } case Class::String: return "\"" + json_escape( *internal.String() ) + "\""; case Class::Floating: return std::to_string( *internal.Float() ); case Class::Integral: return std::to_string( *internal.Int() ); case Class::Boolean: return *internal.Bool() ? "true" : "false"; } throw std::runtime_error("Unhandled JSON type"); } private: static std::string json_escape( const std::string &str ) { std::string output; for(char i : str) { switch( i ) { case '\"': output += "\\\""; break; case '\\': output += "\\\\"; break; case '\b': output += "\\b"; break; case '\f': output += "\\f"; break; case '\n': output += "\\n"; break; case '\r': output += "\\r"; break; case '\t': output += "\\t"; break; default : output += i; break; } } return output; } private: }; struct JSONParser { static bool isspace(const char c) noexcept { #ifdef CHAISCRIPT_MSVC // MSVC warns on these line in some circumstances #pragma warning(push) #pragma warning(disable : 6330) #endif return ::isspace(c) != 0; #ifdef CHAISCRIPT_MSVC #pragma warning(pop) #endif } static void consume_ws( const std::string &str, size_t &offset ) { while( isspace( str.at(offset) ) && offset <= str.size() ) { ++offset; } } static JSON parse_object( const std::string &str, size_t &offset ) { JSON Object( JSON::Class::Object ); ++offset; consume_ws( str, offset ); if( str.at(offset) == '}' ) { ++offset; return Object; } for (;offset= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') ) { val += c; } else { throw std::runtime_error(std::string("JSON ERROR: String: Expected hex character in unicode escape, found '") + c + "'"); } } offset += 4; } break; default : val += '\\'; break; } } else { val += c; } } ++offset; return JSON(val); } static JSON parse_number( const std::string &str, size_t &offset ) { std::string val, exp_str; char c = '\0'; bool isDouble = false; bool isNegative = false; int64_t exp = 0; bool isExpNegative = false; if( offset < str.size() && str.at(offset) == '-' ) { isNegative = true; ++offset; } for (; offset < str.size() ;) { c = str.at(offset++); if( c >= '0' && c <= '9' ) { val += c; } else if( c == '.' && !isDouble ) { val += c; isDouble = true; } else { break; } } if( offset < str.size() && (c == 'E' || c == 'e' )) { c = str.at(offset++); if( c == '-' ) { isExpNegative = true; } else if( c == '+' ) { // do nothing } else { --offset; } for (; offset < str.size() ;) { c = str.at(offset++); if( c >= '0' && c <= '9' ) { exp_str += c; } else if( !isspace( c ) && c != ',' && c != ']' && c != '}' ) { throw std::runtime_error(std::string("JSON ERROR: Number: Expected a number for exponent, found '") + c + "'"); } else { break; } } exp = chaiscript::parse_num( exp_str ) * (isExpNegative?-1:1); } else if( offset < str.size() && (!isspace( c ) && c != ',' && c != ']' && c != '}' )) { throw std::runtime_error(std::string("JSON ERROR: Number: unexpected character '") + c + "'"); } --offset; if( isDouble ) { return JSON((isNegative?-1:1) * chaiscript::parse_num( val ) * std::pow( 10, exp )); } else { if( !exp_str.empty() ) { return JSON((isNegative?-1:1) * static_cast(chaiscript::parse_num( val )) * std::pow( 10, exp )); } else { return JSON((isNegative?-1:1) * chaiscript::parse_num( val )); } } } static JSON parse_bool( const std::string &str, size_t &offset ) { if( str.substr( offset, 4 ) == "true" ) { offset += 4; return JSON(true); } else if( str.substr( offset, 5 ) == "false" ) { offset += 5; return JSON(false); } else { throw std::runtime_error(std::string("JSON ERROR: Bool: Expected 'true' or 'false', found '") + str.substr( offset, 5 ) + "'"); } } static JSON parse_null( const std::string &str, size_t &offset ) { if( str.substr( offset, 4 ) != "null" ) { throw std::runtime_error(std::string("JSON ERROR: Null: Expected 'null', found '") + str.substr( offset, 4 ) + "'"); } offset += 4; return JSON(); } static JSON parse_next( const std::string &str, size_t &offset ) { char value; consume_ws( str, offset ); value = str.at(offset); switch( value ) { case '[' : return parse_array( str, offset ); case '{' : return parse_object( str, offset ); case '\"': return parse_string( str, offset ); case 't' : case 'f' : return parse_bool( str, offset ); case 'n' : return parse_null( str, offset ); default : if( ( value <= '9' && value >= '0' ) || value == '-' ) { return parse_number( str, offset ); } } throw std::runtime_error(std::string("JSON ERROR: Parse: Unexpected starting character '") + value + "'"); } }; inline JSON JSON::Load( const std::string &str ) { size_t offset = 0; return JSONParser::parse_next( str, offset ); } } // End Namespace json #endif