diff options
Diffstat (limited to 'source/core/StarJson.hpp')
-rw-r--r-- | source/core/StarJson.hpp | 358 |
1 files changed, 358 insertions, 0 deletions
diff --git a/source/core/StarJson.hpp b/source/core/StarJson.hpp new file mode 100644 index 0000000..f14fbf8 --- /dev/null +++ b/source/core/StarJson.hpp @@ -0,0 +1,358 @@ +#ifndef STAR_JSON_HPP +#define STAR_JSON_HPP + +#include "StarDataStream.hpp" +#include "StarVariant.hpp" +#include "StarString.hpp" + +namespace Star { + +STAR_EXCEPTION(JsonException, StarException); +STAR_EXCEPTION(JsonParsingException, StarException); + +STAR_CLASS(Json); + +typedef List<Json> JsonArray; +typedef shared_ptr<JsonArray const> JsonArrayConstPtr; + +typedef StringMap<Json> JsonObject; +typedef shared_ptr<JsonObject const> JsonObjectConstPtr; + +// Class for holding representation of JSON data. Immutable and implicitly +// shared. +class Json { +public: + template <typename Container> + struct IteratorWrapper { + typedef typename Container::const_iterator const_iterator; + typedef const_iterator iterator; + + const_iterator begin() const; + const_iterator end() const; + + shared_ptr<Container const> ptr; + }; + + enum class Type : uint8_t { + Null = 0, + Float = 1, + Bool = 2, + Int = 3, + String = 4, + Array = 5, + Object = 6 + }; + + static String typeName(Type t); + static Type typeFromName(String const& t); + + static Json ofType(Type t); + + // Parses JSON or JSON sub-type + static Json parse(String const& string); + + // Parses JSON object or array only (the only top level types allowed by + // JSON) + static Json parseJson(String const& json); + + // Constructs type Null + Json(); + + Json(double); + Json(bool); + Json(int); + Json(long); + Json(long long); + Json(unsigned int); + Json(unsigned long); + Json(unsigned long long); + Json(char const*); + Json(String::Char const*); + Json(String::Char const*, size_t); + Json(String); + Json(std::string); + Json(JsonArray); + Json(JsonObject); + + // Float and Int types are convertible between each other. toDouble, + // toFloat, toInt, toUInt may be called on either an Int or a Float. For a + // Float this is simply a C style cast from double, and for an Int it is + // simply a C style cast from int64_t. + // + // Bools, Strings, Arrays, Objects, and Null are not automatically + // convertible to any other type. + + double toDouble() const; + float toFloat() const; + bool toBool() const; + int64_t toInt() const; + uint64_t toUInt() const; + String toString() const; + JsonArray toArray() const; + JsonObject toObject() const; + + // Internally, String, JsonArray, and JsonObject are shared via shared_ptr + // since this class is immutable. Use these methods to get at this pointer + // without causing a copy. + StringConstPtr stringPtr() const; + JsonArrayConstPtr arrayPtr() const; + JsonObjectConstPtr objectPtr() const; + + // As a convenience, make it easy to safely and quickly iterate over a + // JsonArray or JsonObject contents by holding the container pointer. + IteratorWrapper<JsonArray> iterateArray() const; + IteratorWrapper<JsonObject> iterateObject() const; + + // opt* methods work like this, if the json is null, it returns none. If the + // json is convertible, it returns the converted type, otherwise an exception + // occurrs. + Maybe<Json> opt() const; + Maybe<double> optDouble() const; + Maybe<float> optFloat() const; + Maybe<bool> optBool() const; + Maybe<int64_t> optInt() const; + Maybe<uint64_t> optUInt() const; + Maybe<String> optString() const; + Maybe<JsonArray> optArray() const; + Maybe<JsonObject> optObject() const; + + // Size of array / object type json + size_t size() const; + + // If this json is array type, get the value at the given index + Json get(size_t index) const; + double getDouble(size_t index) const; + float getFloat(size_t index) const; + bool getBool(size_t index) const; + int64_t getInt(size_t index) const; + uint64_t getUInt(size_t index) const; + String getString(size_t index) const; + JsonArray getArray(size_t index) const; + JsonObject getObject(size_t index) const; + + // These versions of get* return default value if the index is out of range, + // or if the value pointed to is null. + Json get(size_t index, Json def) const; + double getDouble(size_t index, double def) const; + float getFloat(size_t index, float def) const; + bool getBool(size_t index, bool def) const; + int64_t getInt(size_t index, int64_t def) const; + uint64_t getUInt(size_t index, int64_t def) const; + String getString(size_t index, String def) const; + JsonArray getArray(size_t index, JsonArray def) const; + JsonObject getObject(size_t index, JsonObject def) const; + + // If object type, whether object contains key + bool contains(String const& key) const; + + // If this json is object type, get the value for the given key + Json get(String const& key) const; + double getDouble(String const& key) const; + float getFloat(String const& key) const; + bool getBool(String const& key) const; + int64_t getInt(String const& key) const; + uint64_t getUInt(String const& key) const; + String getString(String const& key) const; + JsonArray getArray(String const& key) const; + JsonObject getObject(String const& key) const; + + // These versions of get* return the default if the key is missing or the + // value is null. + Json get(String const& key, Json def) const; + double getDouble(String const& key, double def) const; + float getFloat(String const& key, float def) const; + bool getBool(String const& key, bool def) const; + int64_t getInt(String const& key, int64_t def) const; + uint64_t getUInt(String const& key, int64_t def) const; + String getString(String const& key, String def) const; + JsonArray getArray(String const& key, JsonArray def) const; + JsonObject getObject(String const& key, JsonObject def) const; + + // Works the same way as opt methods above. Will never return a null value, + // if there is a null entry it will just return an empty Maybe. + Maybe<Json> opt(String const& key) const; + Maybe<double> optDouble(String const& key) const; + Maybe<float> optFloat(String const& key) const; + Maybe<bool> optBool(String const& key) const; + Maybe<int64_t> optInt(String const& key) const; + Maybe<uint64_t> optUInt(String const& key) const; + Maybe<String> optString(String const& key) const; + Maybe<JsonArray> optArray(String const& key) const; + Maybe<JsonObject> optObject(String const& key) const; + + // Combines gets recursively in friendly expressions. For + // example, call like this: json.query("path.to.array[3][4]") + Json query(String const& path) const; + double queryDouble(String const& path) const; + float queryFloat(String const& path) const; + bool queryBool(String const& path) const; + int64_t queryInt(String const& path) const; + uint64_t queryUInt(String const& path) const; + String queryString(String const& path) const; + JsonArray queryArray(String const& path) const; + JsonObject queryObject(String const& path) const; + + // These versions of get* do not throw on missing / null keys anywhere in the + // query path. + Json query(String const& path, Json def) const; + double queryDouble(String const& path, double def) const; + float queryFloat(String const& path, float def) const; + bool queryBool(String const& path, bool def) const; + int64_t queryInt(String const& path, int64_t def) const; + uint64_t queryUInt(String const& path, uint64_t def) const; + String queryString(String const& path, String def) const; + JsonArray queryArray(String const& path, JsonArray def) const; + JsonObject queryObject(String const& path, JsonObject def) const; + + // Returns none on on missing / null keys anywhere in the query path. Will + // never return a null value, just an empty Maybe. + Maybe<Json> optQuery(String const& path) const; + Maybe<double> optQueryDouble(String const& path) const; + Maybe<float> optQueryFloat(String const& path) const; + Maybe<bool> optQueryBool(String const& path) const; + Maybe<int64_t> optQueryInt(String const& path) const; + Maybe<uint64_t> optQueryUInt(String const& path) const; + Maybe<String> optQueryString(String const& path) const; + Maybe<JsonArray> optQueryArray(String const& path) const; + Maybe<JsonObject> optQueryObject(String const& path) const; + + // Returns a *new* object with the given values set/erased. Throws if not an + // object. + Json set(String key, Json value) const; + Json setPath(String path, Json value) const; + Json setAll(JsonObject values) const; + Json eraseKey(String key) const; + Json erasePath(String path) const; + + // Returns a *new* array with the given values set/inserted/appended/erased. + // Throws if not an array. + Json set(size_t index, Json value) const; + Json insert(size_t index, Json value) const; + Json append(Json value) const; + Json eraseIndex(size_t index) const; + + Type type() const; + String typeName() const; + Json convert(Type u) const; + + bool isType(Type type) const; + bool canConvert(Type type) const; + + // isNull returns true when the type of the Json is null. operator bool() is + // the opposite of isNull(). + bool isNull() const; + explicit operator bool() const; + + // Prints JSON or JSON sub-type. If sort is true, then any object anywhere + // inside this value will be sorted alphanumerically before being written, + // resulting in a known *unique* textual representation of the Json that is + // cross-platform. + String repr(int pretty = 0, bool sort = false) const; + // Prints JSON object or array only (only top level types allowed by JSON) + String printJson(int pretty = 0, bool sort = false) const; + + // operator== and operator!= compare for exact equality with all types, and + // additionally equality with numeric conversion with Int <-> Float + bool operator==(Json const& v) const; + bool operator!=(Json const& v) const; + + // Does this Json not share its storage with any other Json? + bool unique() const; + +private: + Json const* ptr(size_t index) const; + Json const* ptr(String const& key) const; + + Variant<Empty, double, bool, int64_t, StringConstPtr, JsonArrayConstPtr, JsonObjectConstPtr> m_data; +}; + +std::ostream& operator<<(std::ostream& os, Json const& v); + +// Fixes ambiguity with OrderedHashMap operator<< +std::ostream& operator<<(std::ostream& os, JsonObject const& v); + +// Serialize json to DataStream. Strings are stored as UTF-8, ints are stored +// as VLQ, doubles as 64 bit. +DataStream& operator<<(DataStream& ds, Json const& v); +DataStream& operator>>(DataStream& ds, Json& v); + +// Convenience methods for Json containers +DataStream& operator<<(DataStream& ds, JsonArray const& l); +DataStream& operator>>(DataStream& ds, JsonArray& l); +DataStream& operator<<(DataStream& ds, JsonObject const& m); +DataStream& operator>>(DataStream& ds, JsonObject& m); + +// Merges the two given json values and returns the result, by the following +// rules (applied in order): If the base value is null, returns the merger. +// If the merger value is null, returns base. For any two non-objects types, +// returns the merger. If both values are objects, then the resulting object +// is the combination of both objects, but for each repeated key jsonMerge is +// called recursively on both values to determine the result. +Json jsonMerge(Json const& base, Json const& merger); + +template <typename... T> +Json jsonMerge(Json const& base, Json const& merger, T const&... rest); + +// Similar to jsonMerge, but query only for a single key. Gets a value equal +// to jsonMerge(jsons...).query(key, Json()), but much faster than doing an +// entire merge operation. +template <typename... T> +Json jsonMergeQuery(String const& key, Json const& first, T const&... rest); + +// jsonMergeQuery with a default. +template <typename... T> +Json jsonMergeQueryDef(String const& key, Json def, Json const& first, T const&... rest); + +template <> +struct hash<Json> { + size_t operator()(Json const& v) const; +}; + +template <typename Container> +auto Json::IteratorWrapper<Container>::begin() const -> const_iterator { + return ptr->begin(); +} + +template <typename Container> +auto Json::IteratorWrapper<Container>::end() const -> const_iterator { + return ptr->end(); +} + +template <typename... T> +Json jsonMerge(Json const& base, Json const& merger, T const&... rest) { + return jsonMerge(jsonMerge(base, merger), rest...); +} + +template <typename... T> +Json jsonMergeQuery(String const&, Json def) { + return def; +} + +template <typename... T> +Json jsonMergeQueryImpl(String const& key, Json const& json) { + return json.query(key, {}); +} + +template <typename... T> +Json jsonMergeQueryImpl(String const& key, Json const& base, Json const& first, T const&... rest) { + Json value = jsonMergeQueryImpl(key, first, rest...); + if (value && !value.isType(Json::Type::Object)) + return value; + return jsonMerge(base.query(key, {}), value); +} + +template <typename... T> +Json jsonMergeQuery(String const& key, Json const& first, T const&... rest) { + return jsonMergeQueryImpl(key, first, rest...); +} + +template <typename... T> +Json jsonMergeQueryDef(String const& key, Json def, Json const& first, T const&... rest) { + if (auto v = jsonMergeQueryImpl(key, first, rest...)) + return v; + return def; +} + +} + +#endif |