#pragma once #include "StarDataStream.hpp" #include "StarVariant.hpp" #include "StarString.hpp" #include "StarXXHash.hpp" namespace Star { STAR_EXCEPTION(JsonException, StarException); STAR_EXCEPTION(JsonParsingException, StarException); STAR_CLASS(Json); typedef List JsonArray; typedef shared_ptr JsonArrayConstPtr; typedef StringMap JsonObject; typedef shared_ptr JsonObjectConstPtr; // Class for holding representation of JSON data. Immutable and implicitly // shared. class Json { public: template struct IteratorWrapper { typedef typename Container::const_iterator const_iterator; typedef const_iterator iterator; const_iterator begin() const; const_iterator end() const; shared_ptr 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 iterateArray() const; IteratorWrapper 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 opt() const; Maybe optDouble() const; Maybe optFloat() const; Maybe optBool() const; Maybe optInt() const; Maybe optUInt() const; Maybe optString() const; Maybe optArray() const; Maybe 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 opt(String const& key) const; Maybe optDouble(String const& key) const; Maybe optFloat(String const& key) const; Maybe optBool(String const& key) const; Maybe optInt(String const& key) const; Maybe optUInt(String const& key) const; Maybe optString(String const& key) const; Maybe optArray(String const& key) const; Maybe 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 const& 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 optQuery(String const& path) const; Maybe optQueryDouble(String const& path) const; Maybe optQueryFloat(String const& path) const; Maybe optQueryBool(String const& path) const; Maybe optQueryInt(String const& path) const; Maybe optQueryUInt(String const& path) const; Maybe optQueryString(String const& path) const; Maybe optQueryArray(String const& path) const; Maybe 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; void getHash(XXHash3& hasher) const; private: Json const* ptr(size_t index) const; Json const* ptr(String const& key) const; Variant 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 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 Json jsonMergeQuery(String const& key, Json const& first, T const&... rest); // jsonMergeQuery with a default. template Json jsonMergeQueryDef(String const& key, Json def, Json const& first, T const&... rest); template <> struct hash { size_t operator()(Json const& v) const; }; template auto Json::IteratorWrapper::begin() const -> const_iterator { return ptr->begin(); } template auto Json::IteratorWrapper::end() const -> const_iterator { return ptr->end(); } template Json jsonMerge(Json const& base, Json const& merger, T const&... rest) { return jsonMerge(jsonMerge(base, merger), rest...); } template Json jsonMergeQuery(String const&, Json def) { return def; } template Json jsonMergeQueryImpl(String const& key, Json const& json) { return json.query(key, {}); } template 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 Json jsonMergeQuery(String const& key, Json const& first, T const&... rest) { return jsonMergeQueryImpl(key, first, rest...); } template Json jsonMergeQueryDef(String const& key, Json def, Json const& first, T const&... rest) { if (auto v = jsonMergeQueryImpl(key, first, rest...)) return v; return def; } // Compares two JSON values to see if the second is a subset of the first. // For objects, each key in the second object must exist in the first // object and the values are recursively compared the same way. For arrays, // each element in the second array must successfully compare with some // element of the first array, regardless of order or duplication. // For all other types, the values must be equal. bool jsonPartialMatch(Json const& base, Json const& compare); } template <> struct fmt::formatter : ostream_formatter {}; template <> struct fmt::formatter : ostream_formatter {};