// json.h - v1.0 - Single-header public domain JSON library // // The intent of this library is to provide a simple and convenient in-memory // representation of JSON values, and provide both parsing and printing // functionality for round-trip conversions between JSON-encoded text and in- // memory values. // // The original author of this software is Sterling Orsten, and its permanent // home is . If you find this software // useful, an acknowledgement in your source text and/or product documentation // is appreciated, but not required. // This is free and unencumbered software released into the public domain. // // Anyone is free to copy, modify, publish, use, compile, sell, or // distribute this software, either in source code form or as a compiled // binary, for any purpose, commercial or non-commercial, and by any // means. // // In jurisdictions that recognize copyright laws, the author or authors // of this software dedicate any and all copyright interest in the // software to the public domain. We make this dedication for the benefit // of the public at large and to the detriment of our heirs and // successors. We intend this dedication to be an overt act of // relinquishment in perpetuity of all present and future rights to this // software under copyright law. // // 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 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. // // For more information, please refer to #ifndef JSON_H #define JSON_H #include // For int32_t, etc. #include // For assert(...) #include // For std::array #include // For std::vector #include // For std::map #include // For std::runtime_error #include // For std::ostringstream namespace json { enum class kind { string, number, object, array, true_, false_, null }; // Describes one of seven distinct categories of JSON values class value; // Stores a JSON value typedef std::vector array; // Stores a JSON array (an ordered list of values) typedef std::map object; // Stores a JSON object (a collection of name/value pairs) struct parse_error : std::runtime_error { parse_error(const std::string & what) : runtime_error("json parse error - " + what) {} }; value parse(const std::string & text); // throws parse_error bool is_json_number(const std::string & num); class value { kind k; std::string str; // Contents of String or Number value object obj; // Fields of Object value array arr; // Elements of Array value value(kind k, std::string str) : k(k), str(move(str)) {} public: value() : k(kind::null) {} // Default construct null value(const char * s) : value(kind::string, s) {} // Construct string from C-string value(std::string s) : value(kind::string, move(s)) {} // Construct string from std::string template::value, void>::type> value(N n) : k(kind::number) { std::ostringstream ss; ss << +n; str = ss.str(); } value(object o) : k(kind::object), obj(move(o)) {} // Construct object from vector> (TODO: Assert no duplicate keys) value(array a) : k(kind::array), arr(move(a)) {} // Construct array from vector value(bool b) : k(b ? kind::true_ : kind::false_) {} // Construct true or false from boolean value(std::nullptr_t) : k(kind::null) {} // Construct null from nullptr const value & operator [] (size_t index) const { const static value null; return index < arr.size() ? arr[index] : null; } const value & operator [] (int index) const { const static value null; return index < 0 ? null : operator[](static_cast(index)); } const value & operator [] (const char * key) const { for (auto & kvp : obj) if (kvp.first == key) return kvp.second; const static value null; return null; } const value & operator [] (const std::string & key) const { return operator[](key.c_str()); } kind get_kind() const { return k; } const std::string & get_contents() const { return str; } // Contents, if a String, JSON format number, if a Number, empty otherwise const object & get_object() const { return obj; } // Name/value pairs, if an Object, empty otherwise const array & get_array() const { return arr; } // Values, if an Array, empty otherwise bool is_string() const { return k == kind::string; } bool is_number() const { return k == kind::number; } bool is_object() const { return k == kind::object; } bool is_array() const { return k == kind::array; } bool is_true() const { return k == kind::true_; } bool is_false() const { return k == kind::false_; } bool is_null() const { return k == kind::null; } bool bool_or_default(bool def) const { return is_true() ? true : is_false() ? false : def; } std::string string_or_default(const char * def) const { return k == kind::string ? str : def; } template T number_or_default(T def) const { if (!is_number()) return def; auto val = +T(); std::istringstream(str) >> val; return static_cast(val); } std::string string() const { return string_or_default(""); } // Value, if a String, empty otherwise template T number() const { return number_or_default(T()); } // Value, if a Number, empty otherwise static value from_number(std::string num) { assert(is_json_number(num)); return value(kind::number, move(num)); } }; inline bool operator == (const value & a, const value & b) { return a.get_kind() == b.get_kind() && a.get_contents() == b.get_contents() && a.get_object() == b.get_object() && a.get_array() == b.get_array(); } inline bool operator != (const value & a, const value & b) { return !(a == b); } std::ostream & operator << (std::ostream & out, const value & val); std::ostream & operator << (std::ostream & out, const array & arr); std::ostream & operator << (std::ostream & out, const object & obj); template struct tabbed_ref { const T & value; int tabWidth, indent; }; template tabbed_ref tabbed(const T & value, int tabWidth, int indent = 0) { return{ value, tabWidth, indent }; } std::ostream & operator << (std::ostream & out, tabbed_ref val); std::ostream & operator << (std::ostream & out, tabbed_ref arr); std::ostream & operator << (std::ostream & out, tabbed_ref obj); } inline json::value to_json(bool b) { return b; } inline json::value to_json(const std::string & s) { return s; } template typename std::enable_if::value, json::value>::type to_json(T n) { return n; } template json::value to_json(const T (& a)[N]) { json::array r(N); for(int i=0; i json::value to_json(const std::array & a) { json::array r(N); for(size_t i=0; i json::value to_json(const std::vector & v) { json::array r(v.size()); for(size_t i=0; i json::value to_json(const std::map & m) { json::object r; for(auto & p : m) r[p.first] = to_json(p.second); return r; } struct field_encoder { json::object & o; template void operator () (const char * name, const T & field) { o.emplace(name, to_json(field)); } }; template typename std::enable_if::value, json::value>::type to_json(const T & o) { json::object r; visit_fields(const_cast(o), field_encoder{r}); return r; } inline void from_json(bool & b, const json::value & val) { b = val.is_true(); } inline void from_json(std::string & s, const json::value & val) { s = val.string(); } template typename std::enable_if::value>::type from_json(T & n, const json::value & val) { n = val.number(); } template void from_json(T (& a)[N], const json::value & val) { for(int i=0; i void from_json(std::array & a, const json::value & val) { for(size_t i=0; i void from_json(std::vector & v, const json::value & val) { v.resize(val.get_array().size()); for(size_t i=0; i void from_json(std::map & m, const json::value & val) { for(auto & p : val.get_object()) { from_json(m[p.first], p.second); } } struct field_decoder { const json::value & v; template void operator () (const char * name, T & field) { from_json(field, v[name]); } }; template typename std::enable_if::value>::type from_json(T & o, const json::value & val) { visit_fields(o, field_decoder{val}); } #endif // Define JSON_H_IMPLEMENTATION before including json.h in exactly one *.cpp file #ifdef JSON_H_IMPLEMENTATION #include #include namespace json { std::ostream & print_escaped(std::ostream & out, const std::string & str) { // Escape sequences for ", \, and control characters, 0 indicates no escaping needed static const char * escapes[256] = { "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B", "\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F", 0, 0, "\\\"", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "\\\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "\\u007F" }; out << '"'; for (uint8_t ch : str) { if (escapes[ch]) out << escapes[ch]; else out << ch; } return out << '"'; } std::ostream & operator << (std::ostream & out, const array & arr) { int i = 0; out << '['; for (auto & val : arr) out << (i++ ? "," : "") << val; return out << ']'; } std::ostream & operator << (std::ostream & out, const object & obj) { int i = 0; out << '{'; for (auto & kvp : obj) { print_escaped(out << (i++ ? "," : ""), kvp.first) << ':' << kvp.second; } return out << '}'; } std::ostream & operator << (std::ostream & out, const value & val) { if (val.is_null()) return out << "null"; else if (val.is_false()) return out << "false"; else if (val.is_true()) return out << "true"; else if (val.is_string()) return print_escaped(out, val.get_contents()); else if (val.is_number()) return out << val.get_contents(); else if (val.is_array()) return out << val.get_array(); else return out << val.get_object(); } static std::ostream & indent(std::ostream & out, int space, int n = 0) { if (n) out << ','; out << '\n'; for (int i = 0; i < space; ++i) out << ' '; return out; } std::ostream & operator << (std::ostream & out, tabbed_ref arr) { if (std::none_of(begin(arr.value), end(arr.value), [](const value & val) { return val.is_array() || val.is_object(); })) return out << arr.value; else { int space = arr.indent + arr.tabWidth, i = 0; out << '['; for (auto & val : arr.value) indent(out, space, i++) << tabbed(val, arr.tabWidth, space); return indent(out, arr.indent) << ']'; } } std::ostream & operator << (std::ostream & out, tabbed_ref obj) { if (obj.value.empty()) return out << "{}"; else { int space = obj.indent + obj.tabWidth, i = 0; out << '{'; for (auto & kvp : obj.value) { print_escaped(indent(out, space, i++), kvp.first) << ": " << tabbed(kvp.second, obj.tabWidth, space); } return indent(out, obj.indent) << '}'; } } std::ostream & operator << (std::ostream & out, tabbed_ref val) { if (val.value.is_array()) return out << tabbed(val.value.get_array(), val.tabWidth, val.indent); else if (val.value.is_object()) return out << tabbed(val.value.get_object(), val.tabWidth, val.indent); else return out << val.value; } bool is_json_number(const std::string & num) { #ifdef __GNUC__ auto it=begin(num); if(it == end(num)) return false; // String cannot be empty if(*it == '-') ++it; if(it == end(num)) return false; // Discard optional - at start of string if(*it == '0') ++it; // Whole number part can be 0 else if(isdigit(*it)) { while(it != end(num) && isdigit(*it)) ++it; } // or [1-9][0-9]* else return false; // but anything else fails the match if(it == end(num)) return true; // Acceptable to stop here if(*it == '.') // If there is a . { ++it; if(it == end(num)) return false; // We need at least one more digit while(it != end(num) && isdigit(*it)) ++it; // Skip all digits if(it == end(num)) return true; // Acceptable to stop here } if(*it == 'e' || *it == 'E') // If there is an e or an E { ++it; if(it == end(num)) return false; // We need the exponent term if(*it == '+' || *it == '-') ++it; // Skip +/- if(it == end(num)) return false; // We still need the exponent term while(it != end(num) && isdigit(*it)) ++it; // Skip all digits if(it == end(num)) return true; // Acceptable to stop here } return false; // Anything left over fails the match #else static const std::regex regex(R"(-?(0|([1-9][0-9]*))((\.[0-9]+)?)(((e|E)((\+|-)?)[0-9]+)?))"); return std::regex_match(begin(num), end(num), regex); #endif } static uint16_t decode_hex(char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; if (ch >= 'A' && ch <= 'F') return 10 + ch - 'A'; if (ch >= 'a' && ch <= 'f') return 10 + ch - 'a'; throw parse_error(std::string("invalid hex digit: ") + ch); } static std::string decode_string(std::string::const_iterator first, std::string::const_iterator last) { if (std::any_of(first, last, iscntrl)) throw parse_error("control character found in string literal"); if (std::find(first, last, '\\') == last) return std::string(first, last); // No escape characters, use the string directly std::string s; s.reserve(last - first); // Reserve enough memory to hold the entire string for (; first < last; ++first) { if (*first != '\\') s.push_back(*first); else switch (*(++first)) { case '"': s.push_back('"'); break; case '\\': s.push_back('\\'); break; case '/': s.push_back('/'); break; case 'b': s.push_back('\b'); break; case 'f': s.push_back('\f'); break; case 'n': s.push_back('\n'); break; case 'r': s.push_back('\r'); break; case 't': s.push_back('\t'); break; case 'u': if (first + 5 > last) throw parse_error("incomplete escape sequence: " + std::string(first - 1, last)); else { uint16_t val = (decode_hex(first[1]) << 12) | (decode_hex(first[2]) << 8) | (decode_hex(first[3]) << 4) | decode_hex(first[4]); if (val < 0x80) s.push_back(static_cast(val)); // ASCII codepoint, no translation needed else if (val < 0x800) // 2-byte UTF-8 encoding { s.push_back(0xC0 | ((val >> 6) & 0x1F)); // Leading byte: 5 content bits s.push_back(0x80 | ((val >> 0) & 0x3F)); // Continuation byte: 6 content bits } else // 3-byte UTF-8 encoding (16 content bits, sufficient to store all \uXXXX patterns) { s.push_back(0xE0 | ((val >> 12) & 0x0F)); // Leading byte: 4 content bits s.push_back(0x80 | ((val >> 6) & 0x3F)); // Continuation byte: 6 content bits s.push_back(0x80 | ((val >> 0) & 0x3F)); // Continuation byte: 6 content bits } first += 4; } break; default: throw parse_error("invalid escape sequence"); } } return s; } struct token { char type; std::string value; token(char type, std::string value = std::string()) : type(type), value(move(value)) {} }; struct parse_state { std::vector::iterator it, last; bool match_and_discard(char type) { if (it->type != type) return false; ++it; return true; } void discard_expected(char type, const char * what) { if (!match_and_discard(type)) throw parse_error(std::string("Syntax error: Expected ") + what); } value parse_value() { auto token = it++; switch (token->type) { case 'n': return nullptr; case 'f': return false; case 't': return true; case '"': return token->value; case '#': return value::from_number(token->value); case '[': if (match_and_discard(']')) return array{}; else { array arr; while (true) { arr.push_back(parse_value()); if (match_and_discard(']')) return arr; discard_expected(',', ", or ]"); } } case '{': if (match_and_discard('}')) return object{}; else { object obj; while (true) { auto name = move(it->value); discard_expected('"', "string"); discard_expected(':', ":"); obj.emplace(move(name), parse_value()); if (match_and_discard('}')) { return obj; } discard_expected(',', ", or }"); } } default: throw parse_error("Expected value"); } } }; std::vector parse_tokens(const std::string & text) { std::vector tokens; auto it = begin(text); while (true) { it = std::find_if_not(it, end(text), isspace); // Skip whitespace if (it == end(text)) { tokens.emplace_back('$'); return tokens; } switch (*it) { case '[': case ']': case ',': case '{': case '}': case ':': tokens.push_back({ *it++ }); break; case '"': { auto it2 = ++it; for (; it2 < end(text); ++it2) { if (*it2 == '"') break; if (*it2 == '\\') ++it2; } if (it2 < end(text)) { tokens.emplace_back('"', decode_string(it, it2)); it = it2 + 1; } else throw parse_error("String missing closing quote"); } break; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { auto it2 = std::find_if_not(it, end(text), [](char ch) { return isalnum(ch) || ch == '+' || ch == '-' || ch == '.'; }); auto num = std::string(it, it2); if (!is_json_number(num)) throw parse_error("Invalid number: " + num); tokens.emplace_back('#', move(num)); it = it2; } break; default: if (isalpha(*it)) { auto it2 = std::find_if_not(it, end(text), isalpha); if (std::equal(it, it2, "true")) tokens.emplace_back('t'); else if (std::equal(it, it2, "false")) tokens.emplace_back('f'); else if (std::equal(it, it2, "null")) tokens.emplace_back('n'); else throw parse_error("Invalid token: " + std::string(it, it2)); it = it2; } else throw parse_error("Invalid character: \'" + std::string(1, *it) + '"'); } } } value parse(const std::string & text) { auto tokens = parse_tokens(text); parse_state p = { begin(tokens), end(tokens) }; auto val = p.parse_value(); p.discard_expected('$', "end-of-stream"); return val; } } #endif