Files
json/include/json.hpp
2025-05-31 01:35:33 -05:00

267 lines
11 KiB
C++

/// JJX - Josh's JSON and XML API for C++20
/// Written and maintained by josh@redacted.cc
/// Edit 3 - Revised March 24, 2025
/// (c) 2025 redacted.cc
/// This work is dedicated to the public domain.
#pragma once
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <cassert>
#include <vector>
// TODO: API compatibility with json::value and derived types.
// TODO: Auto-conversion from json::value *to* represented type.
/// Redacted Software JSON API - Parse, Serialize, Validate, and Build JSON files.
namespace json {
std::string format_error(std::string base, std::string source, int index);
/// An enumeration that represents the different types of tokens that exist.
/// A token is an atomic chunk of a JSON file's text, which is the smallest piece of syntax that can be considered in isolation.
/// Tokens may be JSON primitives such as string, number, boolean, null,
/// Or may be a syntax symbol, such as quotations, brackets, commas, etc.
enum class token_type { string, number, syntax, boolean, null };
/// An enumeration that represents the different types of JSON values that can exist.
/// JSON allows for two composite data types: arrays and objects.
/// Arrays are sequential lists of other JSON values, with integer indexing.
/// Objects are collections of named JSON values, with string indexing.
/// string, number, boolean, and null types are considered self-explanatory.
enum class value_type { string, number, object, array, boolean, null};
struct token {
std::string value;
token_type type;
int location;
std::shared_ptr<std::string> full_source;
};
/// The base class for a set of classes representing the various JSON primitives.
struct value;
struct string;
struct number;
struct boolean;
struct object;
struct array;
/// Tokenizes (or Lexes) a string of text into a list of JSON tokens.
/// @return Token list, and an optional error message.
/// @see struct token
std::tuple<std::vector<json::token>, std::string> lex(std::string);
/// Parses a sequence of JSON tokens.
/// @return The root JSON value, which will contain descendant JSON values if applicable,
/// an integer indicating the recursion index, and an error code, if applicable.
std::tuple<json::value, int, std::string> parse(std::vector<json::token>, int index = 0);
/// Parses a sequence of JSON tokens.
/// @return The root JSON value, which will contain descendant JSON values if applicable,
/// an integer indicating the recursion index, and an error code, if applicable.
std::tuple<json::value, std::string> parse(std::string);
std::string deparse(json::value, std::string whitespace = "");
/// This object holds JSON constructs, known as 'values', and associates them to a type.
/// A value represents a single piece of JSON information.
struct value
{
/// This member denotes what type of JSON value this object represents.
value_type type;
/// The following member variables store the actual data this object represents.
/// Only one member at a time should contain any data, depending on the value_type.
std::optional<std::string> string;
std::optional<double> number;
std::optional<bool> boolean;
std::optional<std::vector<value>> array;
std::optional<std::map<std::string, value>> object;
/// The default constructor initializes to a JSON null value.
explicit value();
/// Constructs a json value that represents the corresponding number.
value(double v);
value(int v);
value(float v);
/// Constructs a json value that represents a string.
value(const std::string& v);
value(bool v);
/// Constructs a json value that represents an array.
explicit value(const std::vector<value>& v);
/// Constructs a json value that represents an object.
explicit value(const std::map<std::string, value>& v);
/// Constructs a json value that represents an object, from a key-value pair.
explicit value(const std::pair<std::string, value>& kvp) : object({kvp}), type(value_type::object) {}
/// Constructs a json value that represents an object, from a list of key-value pairs.
//explicit value(const std::vector<std::pair<std::string, value>>& kvp_list);
/// Assigns this value to a number, and sets the value_type::number.
value& operator=(double in);
/// Assigns this value to a string, and sets the value_type::string.
value& operator=(const std::string& in);
/// Assigns this value to a boolean, and sets the value_type::boolean.
value& operator=(bool in);
/// Assigns this value to an array, and sets the value_type::array.
value& operator=(const std::vector<value>& in);
/// Assigns this value to a map, and sets the value_type::map.
value& operator=(const std::map<std::string, value>& in);
explicit operator double() const;
explicit operator std::string() const;
explicit operator bool() const;
explicit operator std::vector<value>() const;
explicit operator std::map<std::string, value>() const;
/// Returns this value's data, converted to a json::object, which is a specialization of this type.
/// @see struct object
[[nodiscard]] struct object as_object() const;
/// Returns this value's data, converted to a json::array, which is a specialization of this type.
[[nodiscard]] struct array as_array() const;
[[nodiscard]] struct string as_string() const;
[[nodiscard]] struct number as_number() const;
[[nodiscard]] struct boolean as_boolean() const;
/// Converts all JSON values contained in this object / array to the derived json value types.
void convert_descendants();
#pragma region Object Access Members
/// @return The json value associated with the given key.
/// @throws std::runtime_error if value_type != object
value& at(const std::string& key);
/// @throws std::runtime_error if value_type != array
value& at(int index);
/// @see object::at()
/// @throws std::runtime_error if value_type != object
value& operator[] (const std::string& key);
/// @throws std::runtime_error if value_type != object
const value& operator[] (const std::string& key) const;
/// @throws std::runtime_error if value_type != array
value& operator[] (int index);
/// @throws std::runtime_error if value_type != array
const value& operator[] (int index) const;
/// @return True if a key-value pair in this object has a key that matches the given value.
bool contains(const std::string& key);
#pragma endregion
};
/// A specialized json::value which provides STL-compatibility with std::string, and other functions for convenience.
struct string : value {
/// The default constructor initializes to an empty string literal.
explicit string() : value("") {}
explicit string(const std::string& v) : value(v) {}
operator std::string();
};
struct number : value {
/// The default constructor initializes to floating-point literal zero.
explicit number();
explicit number(double v);
};
struct boolean : value {
/// The default constructor initializes to false.
explicit boolean();
explicit boolean(bool v);
};
/// A specialized json::value which provides STL compatibility with std::map, and other functions for convenience.
/// As per JSON specification, only std::strings are allowed to be used as keys in this data type.
struct object : value
{
/// The default constructor initializes to an empty map.
explicit object();
/// Constructs this object from a list of key-value pairs, in the form of std::map.
explicit object(const std::map<std::string, value>& v);
/// Adds a key-value pair to this object.
void insert(const std::pair<std::string, value>& kvp);
/// Adds a key-value pair to this object.
/// @param key The key to associate with the value.
void insert(const std::string& key, const value& val);
/// Shorthand operator for adding a key-value pair to this object,
json::object& operator+=(const std::pair<std::string, value>& kvp);
/// Shorthand operator for adding a boolean-value to this object, with a string key.
/*json::object& operator+=(const std::pair<std::string, bool>& kvp);
json::object& operator+=(const std::pair<std::string, double>& kvp);
json::object& operator+=(const std::pair<std::string, std::string>& kvp);
*/
/// @return The json value associated with the given key.
value& at(const std::string& key);
/// @see object::at()
value& operator[] (const std::string& key);
const value& operator[] (const std::string& key) const;
/// @return True if a key-value pair in this object has a key that matches the given value.
bool contains(const std::string& key);
/// @return The json-type of the value of the first key-value pair where the key matches the given string.
json::value_type type_of(const std::string& key);
using map_spec = std::map<std::string, value>;
using iterator = map_spec::iterator;
using const_iterator = map_spec::const_iterator;
iterator begin() { return value::object->begin();}
iterator end() { return value::object->end();}
const_iterator begin() const { return value::object->cbegin(); }
const_iterator end() const { return value::object->cend(); }
size_t size() const { return value::object->size();}
bool empty() const { return value::object->empty(); }
};
struct array : value {
/// The default constructor
explicit array();
explicit array(const std::vector<value>& v);
/// Default Copy Constructor
explicit array(const array& v) = default;
void push_back(const value& element);
size_t size() const;
value& operator+= (const value& v);
value& operator+= (bool v) { push_back(json::boolean(v)); return *this; }
value& operator+= (double v) { push_back(json::number(v)); return *this; }
value& operator+= (const std::string& v) { push_back(json::string(v)); return *this; }
value& operator[] (int key);
json::value_type type_of(int index);
using iterator = std::vector<value>::iterator;
using const_iterator = std::vector<value>::const_iterator;
iterator begin() { return value::array->begin(); }
iterator end() { return value::array->end(); }
[[nodiscard]] const_iterator begin() const;
[[nodiscard]] const_iterator end() const;
[[nodiscard]] const_iterator cbegin() const;
[[nodiscard]] const_iterator cend() const;
};
}