// From github.com/nbsdx/SimpleJSON. // Released under the DWTFYW PL // #ifndef SIMPLEJSON_HPP #define SIMPLEJSON_HPP #include "../chaiscript_defines.hpp" #include "quick_flat_map.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace chaiscript::json { using std::enable_if; using std::initializer_list; using std::is_convertible; using std::is_floating_point; using std::is_integral; using std::is_same; class JSON { public: enum class Class { Null = 0, Object, Array, String, Floating, Integral, Boolean }; private: using Data = std::variant, std::vector, std::string, double, std::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 std::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{}; }); } std::int64_t to_int() const noexcept { return internal.visit_or([](const auto &o) { return o; }, []() { return std::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 + "\"" + json_escape(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 < str.size();) { JSON Key = parse_next(str, offset); consume_ws(str, offset); if (str.at(offset) != ':') { throw std::runtime_error(std::string("JSON ERROR: Object: Expected colon, found '") + str.at(offset) + "'\n"); } consume_ws(str, ++offset); JSON Value = parse_next(str, offset); Object[Key.to_string()] = Value; consume_ws(str, offset); if (str.at(offset) == ',') { ++offset; continue; } else if (str.at(offset) == '}') { ++offset; break; } else { throw std::runtime_error(std::string("JSON ERROR: Object: Expected comma, found '") + str.at(offset) + "'\n"); } } return Object; } static JSON parse_array(const std::string &str, size_t &offset) { JSON Array(JSON::Class::Array); size_t index = 0; ++offset; consume_ws(str, offset); if (str.at(offset) == ']') { ++offset; return Array; } for (; offset < str.size();) { Array[index++] = parse_next(str, offset); consume_ws(str, offset); if (str.at(offset) == ',') { ++offset; continue; } else if (str.at(offset) == ']') { ++offset; break; } else { throw std::runtime_error(std::string("JSON ERROR: Array: Expected ',' or ']', found '") + str.at(offset) + "'\n"); } } return Array; } static JSON parse_string(const std::string &str, size_t &offset) { std::string val; for (char c = str.at(++offset); c != '\"'; c = str.at(++offset)) { if (c == '\\') { switch (str.at(++offset)) { case '\"': val += '\"'; break; case '\\': val += '\\'; break; case '/': val += '/'; break; case 'b': val += '\b'; break; case 'f': val += '\f'; break; case 'n': val += '\n'; break; case 'r': val += '\r'; break; case 't': val += '\t'; break; case 'u': { val += "\\u"; for (size_t i = 1; i <= 4; ++i) { c = str.at(offset + i); if ((c >= '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; std::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); } } // namespace chaiscript::json #endif