mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2025-12-07 01:06:54 +08:00
605 lines
18 KiB
C++
605 lines
18 KiB
C++
// From github.com/nbsdx/SimpleJSON.
|
|
// Released under the DWTFYW PL
|
|
//
|
|
|
|
|
|
|
|
#ifndef SIMPLEJSON_HPP
|
|
#define SIMPLEJSON_HPP
|
|
|
|
|
|
#include <cstdint>
|
|
#include <cmath>
|
|
#include <cctype>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <type_traits>
|
|
#include <initializer_list>
|
|
#include <ostream>
|
|
#include <iostream>
|
|
#include <variant>
|
|
#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::nullptr_t, chaiscript::utility::QuickFlatMap<std::string, JSON>, std::vector<JSON>, 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<typename T> 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<std::string, JSON>{};
|
|
case Class::Array: return std::vector<JSON>{};
|
|
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<auto ClassValue, typename Visitor, typename Or>
|
|
decltype(auto) visit_or(Visitor &&visitor, Or &&other) const
|
|
{
|
|
if (type() == Class(ClassValue)) {
|
|
return visitor(std::get<static_cast<std::size_t>(ClassValue)>(d));
|
|
} else {
|
|
return other();
|
|
}
|
|
}
|
|
|
|
template<auto ClassValue>
|
|
auto &get_set_type() {
|
|
set_type(ClassValue);
|
|
return (std::get<static_cast<std::size_t>(ClassValue)>(d));
|
|
}
|
|
|
|
auto &Map() {
|
|
return get_set_type<Class::Object>();
|
|
}
|
|
auto &Vector() {
|
|
return get_set_type<Class::Array>();
|
|
}
|
|
auto &String() {
|
|
return get_set_type<Class::String>();
|
|
}
|
|
auto &Int() {
|
|
return get_set_type<Class::Integral>();
|
|
}
|
|
auto &Float() {
|
|
return get_set_type<Class::Floating>();
|
|
}
|
|
auto &Bool() {
|
|
return get_set_type<Class::Boolean>();
|
|
}
|
|
|
|
auto Map() const noexcept {
|
|
return std::get_if<static_cast<std::size_t>(Class::Object)>(&d);
|
|
}
|
|
auto Vector() const noexcept {
|
|
return std::get_if<static_cast<std::size_t>(Class::Array)>(&d);
|
|
}
|
|
auto String() const noexcept {
|
|
return std::get_if<static_cast<std::size_t>(Class::String)>(&d);
|
|
}
|
|
auto Int() const noexcept {
|
|
return std::get_if<static_cast<std::size_t>(Class::Integral)>(&d);
|
|
}
|
|
auto Float() const noexcept {
|
|
return std::get_if<static_cast<std::size_t>(Class::Floating)>(&d);
|
|
}
|
|
auto Bool() const noexcept {
|
|
return std::get_if<static_cast<std::size_t>(Class::Boolean)>(&d);
|
|
}
|
|
|
|
Data d;
|
|
};
|
|
|
|
|
|
Internal internal;
|
|
|
|
public:
|
|
|
|
template <typename Container>
|
|
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 <typename Container>
|
|
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<JSON> list )
|
|
: internal(Class::Object)
|
|
{
|
|
for( auto i = list.begin(), e = list.end(); i != e; ++i, ++i ) {
|
|
operator[]( i->to_string() ) = *std::next( i );
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
explicit JSON( T b, typename enable_if<is_same<T,bool>::value>::type* = nullptr ) noexcept : internal( static_cast<bool>(b) ) {}
|
|
|
|
template <typename T>
|
|
explicit JSON( T i, typename enable_if<is_integral<T>::value && !is_same<T,bool>::value>::type* = nullptr ) noexcept : internal( static_cast<std::int64_t>(i) ) {}
|
|
|
|
template <typename T>
|
|
explicit JSON( T f, typename enable_if<is_floating_point<T>::value>::type* = nullptr ) noexcept : internal( static_cast<double>(f) ) {}
|
|
|
|
template <typename T>
|
|
explicit JSON( T s, typename enable_if<is_convertible<T,std::string>::value>::type* = nullptr ) : internal( static_cast<std::string>(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<Class::Object>(
|
|
[&](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<Class::Array>(
|
|
[&](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<Class::Array>(
|
|
[&](const auto &m){ return static_cast<int>(m.size()); },
|
|
[](){ return -1; }
|
|
);
|
|
}
|
|
|
|
bool has_key( const std::string &key ) const noexcept {
|
|
return internal.visit_or<Class::Object>(
|
|
[&](const auto &m){ return m.count(key) != 0; },
|
|
[](){ return false; }
|
|
);
|
|
}
|
|
|
|
int size() const noexcept {
|
|
if (auto m = internal.Map(); m != nullptr) {
|
|
return static_cast<int>(m->size());
|
|
} if (auto v = internal.Vector(); v != nullptr) {
|
|
return static_cast<int>(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<Class::String>(
|
|
[](const auto &o){ return o; },
|
|
[](){ return std::string{}; }
|
|
);
|
|
}
|
|
double to_float() const noexcept {
|
|
return internal.visit_or<Class::Floating>(
|
|
[](const auto &o){ return o; },
|
|
[](){ return double{}; }
|
|
);
|
|
}
|
|
std::int64_t to_int() const noexcept {
|
|
return internal.visit_or<Class::Integral>(
|
|
[](const auto &o){ return o; },
|
|
[](){ return std::int64_t{}; }
|
|
);
|
|
}
|
|
bool to_bool() const noexcept {
|
|
return internal.visit_or<Class::Boolean>(
|
|
[](const auto &o){ return o; },
|
|
[](){ return false; }
|
|
);
|
|
}
|
|
|
|
JSONWrapper<chaiscript::utility::QuickFlatMap<std::string, JSON>> object_range() {
|
|
return std::get_if<static_cast<std::size_t>(Class::Object)>(&internal.d);
|
|
}
|
|
|
|
JSONWrapper<std::vector<JSON>> array_range() {
|
|
return std::get_if<static_cast<std::size_t>(Class::Array)>(&internal.d);
|
|
}
|
|
|
|
JSONConstWrapper<chaiscript::utility::QuickFlatMap<std::string, JSON>> object_range() const {
|
|
return std::get_if<static_cast<std::size_t>(Class::Object)>(&internal.d);
|
|
}
|
|
|
|
JSONConstWrapper<std::vector<JSON>> array_range() const {
|
|
return std::get_if<static_cast<std::size_t>(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<std::int64_t>( 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<double>( val ) * std::pow( 10, exp ));
|
|
} else {
|
|
if( !exp_str.empty() ) {
|
|
return JSON((isNegative?-1:1) * static_cast<double>(chaiscript::parse_num<std::int64_t>( val )) * std::pow( 10, exp ));
|
|
} else {
|
|
return JSON((isNegative?-1:1) * chaiscript::parse_num<std::int64_t>( 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
|