#pragma once #include "Library.h" #include #include #include #include #include #include #include #include #include #include #include namespace CesiumUtility { /** * @brief Attempts a narrowing conversion of `U` into `T` without losing * information. If a lossless conversion can't be performed, `std::nullopt` is * returned. * * @tparam U The type to convert from. * @tparam T The type to convert to. * @param u The value to perform the conversion on. */ template constexpr std::optional losslessNarrow(U u) noexcept { constexpr const bool is_different_signedness = (std::is_signed::value != std::is_signed::value); const T t = static_cast(u); if (static_cast(t) != u || (is_different_signedness && ((t < T{}) != (u < U{})))) { return std::nullopt; } return t; } /** * @brief Attempts a narrowing conversion of `U` into `T` without losing * information. If a lossless conversion can't be performed, `defaultValue` is * returned. * * @tparam U The type to convert from. * @tparam T The type to convert to. * @param u The value to perform the conversion on. * @param defaultValue The value that will be returned if a lossless conversion * can't be performed. */ template constexpr T losslessNarrowOrDefault(U u, T defaultValue) noexcept { constexpr const bool is_different_signedness = (std::is_signed::value != std::is_signed::value); const T t = static_cast(u); if (static_cast(t) != u || (is_different_signedness && ((t < T{}) != (u < U{})))) { return defaultValue; } return t; } /** * @brief A generic implementation of a value in a JSON structure. * * Instances of this class are used to represent the common `extras` field * of glTF elements that extend the the {@link ExtensibleObject} class. */ class CESIUMUTILITY_API JsonValue final { public: /** * @brief The type to represent a `null` JSON value. */ using Null = std::nullptr_t; /** * @brief The type to represent a `Bool` JSON value. */ using Bool = bool; /** * @brief The type to represent a `String` JSON value. */ using String = std::string; /** * @brief The type to represent an `Object` JSON value. */ using Object = std::map; /** * @brief The type to represent an `Array` JSON value. */ using Array = std::vector; /** * @brief Default constructor. */ JsonValue() noexcept : value() {} /** * @brief Creates a `null` JSON value. */ JsonValue(std::nullptr_t) noexcept : value(nullptr) {} /** * @brief Creates a `Number` JSON value. * * NaN and ±Infinity are represented as {@link JsonValue::Null}. */ JsonValue(double v) noexcept { if (std::isnan(v) || std::isinf(v)) { value = nullptr; } else { value = v; } } /** * @brief Creates a `std::int64_t` JSON value (Widening conversion from * std::int8_t). */ JsonValue(std::int8_t v) noexcept : value(static_cast(v)) {} /** * @brief Creates a `std::uint64_t` JSON value (Widening conversion from * std::uint8_t). */ JsonValue(std::uint8_t v) noexcept : value(static_cast(v)) {} /** * @brief Creates a `std::int64_t` JSON value (Widening conversion from * std::int16_t). */ JsonValue(std::int16_t v) noexcept : value(static_cast(v)) {} /** * @brief Creates a `std::uint64_t` JSON value (Widening conversion from * std::uint16_t). */ JsonValue(std::uint16_t v) noexcept : value(static_cast(v)) {} /** * @brief Creates a `std::int64_t` JSON value (Widening conversion from * std::int32_t). */ JsonValue(std::int32_t v) noexcept : value(static_cast(v)) {} /** * @brief Creates a `std::uint64_t` JSON value (Widening conversion from * std::uint32_t). */ JsonValue(std::uint32_t v) noexcept : value(static_cast(v)) {} /** * @brief Creates a `std::int64_t` JSON value. */ JsonValue(std::int64_t v) noexcept : value(v) {} /** * @brief Creates a `std::uint64_t` JSON value. */ JsonValue(std::uint64_t v) noexcept : value(v) {} /** * @brief Creates a `Bool` JSON value. */ JsonValue(bool v) noexcept : value(v) {} /** * @brief Creates a `String` JSON value. */ JsonValue(const std::string& v) : value(v) {} /** * @brief Creates a `String` JSON value. */ JsonValue(std::string&& v) noexcept : value(std::move(v)) {} /** * @brief Creates a `String` JSON value. */ JsonValue(const char* v) : value(std::string(v)) {} /** * @brief Creates an `Object` JSON value with the given properties. */ JsonValue(const std::map& v) : value(v) {} /** * @brief Creates an `Object` JSON value with the given properties. */ JsonValue(std::map&& v) : value(std::move(v)) {} /** * @brief Creates an `Array` JSON value with the given elements. */ JsonValue(const std::vector& v) : value(v) {} /** * @brief Creates an `Array` JSON value with the given elements. */ JsonValue(std::vector&& v) noexcept : value(std::move(v)) {} /** * @brief Creates an JSON value from the given initializer list. */ JsonValue(std::initializer_list v) : value(std::vector(v)) {} /** * @brief Creates an JSON value from the given initializer list. */ JsonValue(std::initializer_list> v) : value(std::map(v)) {} /** * @brief Attempts to obtain a pointer to a \ref JsonValue for the given key * on this object. * * @param key The key to lookup. * @returns A pointer to the \ref JsonValue for the provided key, or * `nullptr`. */ [[nodiscard]] const JsonValue* getValuePtrForKey(const std::string& key) const; /** @copydoc getValuePtrForKey */ [[nodiscard]] JsonValue* getValuePtrForKey(const std::string& key); /** * @brief Gets a typed value corresponding to the given key in the * object represented by this instance. * * If this instance is not a {@link JsonValue::Object}, returns * `nullptr`. If the key does not exist in this object, returns * `nullptr`. If the named value does not have the type T, returns * nullptr. * * @tparam T The expected type of the value. * @param key The key for which to retrieve the value from this object. * @return A pointer to the requested value, or nullptr if the value * cannot be obtained as requested. */ template const T* getValuePtrForKey(const std::string& key) const { const JsonValue* pValue = this->getValuePtrForKey(key); if (!pValue) { return nullptr; } return std::get_if(&pValue->value); } /** * @brief Gets a typed value corresponding to the given key in the * object represented by this instance. * * If this instance is not a {@link JsonValue::Object}, returns * `nullptr`. If the key does not exist in this object, returns * `nullptr`. If the named value does not have the type T, returns * nullptr. * * @tparam T The expected type of the value. * @param key The key for which to retrieve the value from this object. * @return A pointer to the requested value, or nullptr if the value * cannot be obtained as requested. */ template T* getValuePtrForKey(const std::string& key) { JsonValue* pValue = this->getValuePtrForKey(key); return std::get_if(&pValue->value); } /** * @brief Converts the numerical value corresponding to the given key * to the provided numerical template type. * If this instance is not a {@link JsonValue::Object}, the key does not exist * in this object, the named value does not have a numerical type, or if the * named value cannot be converted from `double` / `std::uint64_t` / * `std::int64_t` without precision loss, returns `std::nullopt`. * @tparam To The expected type of the value. * @param key The key for which to retrieve the value from this object. * @return The converted value, or std::nullopt if it cannot be converted for * any of the previously-mentioned reasons. * @remarks Compilation will fail if type 'To' is not an integral / float / * double type. */ template < typename To, typename std::enable_if< std::is_integral::value || std::is_floating_point::value>::type* = nullptr> [[nodiscard]] std::optional getSafeNumericalValueForKey(const std::string& key) const { const Object& pObject = std::get(this->value); const auto it = pObject.find(key); if (it == pObject.end()) { return std::nullopt; } return it->second.getSafeNumber(); } /** * @brief Converts the numerical value corresponding to the given key * to the provided numerical template type. * * If this instance is not a {@link JsonValue::Object}, the key does not exist * in this object, or the named value does not have a numerical type that can * be represented as T without precision loss, then the default value is * returned. * * @tparam To The expected type of the value. * @param key The key for which to retrieve the value from this object. * @param defaultValue The value that will be returned if a numerical value * can't be obtained. * @return The converted value. * @throws If unable to convert the converted value for one of the * aforementioned reasons. * @remarks Compilation will fail if type 'To' is not an integral / float / * double type. */ template < typename To, typename std::enable_if< std::is_integral::value || std::is_floating_point::value>::type* = nullptr> [[nodiscard]] To getSafeNumericalValueOrDefaultForKey( const std::string& key, To defaultValue) const { const Object& pObject = std::get(this->value); const auto it = pObject.find(key); if (it == pObject.end()) { return defaultValue; } return it->second.getSafeNumberOrDefault(defaultValue); } /** * @brief Determines if this value is an Object and has the given key. * * @param key The key. * @return true if this value contains the key. false if it is not an object * or does not contain the given key. */ [[nodiscard]] inline bool hasKey(const std::string& key) const { const Object* pObject = std::get_if(&this->value); if (!pObject) { return false; } return pObject->find(key) != pObject->end(); } /** * @brief Gets the numerical quantity from the value casted to the `To` * type. This function should be used over `getDouble()` / `getUint64()` / * `getInt64()` if you plan on casting that type into another smaller type or * different type. * @returns The converted type if it is numerical and it can be cast without * precision loss; otherwise, std::nullopt. */ template < typename To, typename std::enable_if< std::is_integral::value || std::is_floating_point::value>::type* = nullptr> [[nodiscard]] std::optional getSafeNumber() const { const std::uint64_t* uInt = std::get_if(&this->value); if (uInt) { return losslessNarrow(*uInt); } const std::int64_t* sInt = std::get_if(&this->value); if (sInt) { return losslessNarrow(*sInt); } const double* real = std::get_if(&this->value); if (real) { return losslessNarrow(*real); } return std::nullopt; } /** * @brief Gets the numerical quantity from the value casted to the `To` * type or returns defaultValue if unable to do so. * @returns The converted type if it can be cast without precision loss * or `defaultValue` if it cannot be converted safely. */ template < typename To, typename std::enable_if< std::is_integral::value || std::is_floating_point::value>::type* = nullptr> [[nodiscard]] To getSafeNumberOrDefault(To defaultValue) const noexcept { const std::uint64_t* uInt = std::get_if(&this->value); if (uInt) { return losslessNarrowOrDefault(*uInt, defaultValue); } const std::int64_t* sInt = std::get_if(&this->value); if (sInt) { return losslessNarrowOrDefault(*sInt, defaultValue); } const double* real = std::get_if(&this->value); if (real) { return losslessNarrowOrDefault(*real, defaultValue); } return defaultValue; } /** * @brief Gets the object from the value. * @return The object. * @throws std::bad_variant_access if the underlying type is not a * JsonValue::Object */ [[nodiscard]] inline const JsonValue::Object& getObject() const { return std::get(this->value); } /** * @brief Gets the string from the value. * @return The string. * @throws std::bad_variant_access if the underlying type is not a * JsonValue::String */ [[nodiscard]] inline const JsonValue::String& getString() const { return std::get(this->value); } /** * @brief Gets the array from the value. * @return The array. * @throws std::bad_variant_access if the underlying type is not a * JsonValue::Array */ [[nodiscard]] inline const JsonValue::Array& getArray() const { return std::get(this->value); } /** * @brief Gets an array of strings from the value. * * @param defaultString The default string to include in the array for an * element that is not a string. * @return The array of strings, or an empty array if this value is not an * array at all. */ [[nodiscard]] std::vector getArrayOfStrings(const std::string& defaultString) const; /** * @brief Gets the bool from the value. * @return The bool. * @throws std::bad_variant_access if the underlying type is not a * JsonValue::Bool */ [[nodiscard]] inline bool getBool() const { return std::get(this->value); } /** * @brief Gets the double from the value. * @return The double. * @throws std::bad_variant_access if the underlying type is not a double */ [[nodiscard]] inline double getDouble() const { return std::get(this->value); } /** * @brief Gets the std::uint64_t from the value. * @return The std::uint64_t. * @throws std::bad_variant_access if the underlying type is not a * std::uint64_t */ [[nodiscard]] std::uint64_t getUint64() const { return std::get(this->value); } /** * @brief Gets the std::int64_t from the value. * @return The std::int64_t. * @throws std::bad_variant_access if the underlying type is not a * std::int64_t */ [[nodiscard]] std::int64_t getInt64() const { return std::get(this->value); } /** * @brief Gets the bool from the value or returns defaultValue * @return The bool or defaultValue if this->value is not a bool. */ [[nodiscard]] inline bool getBoolOrDefault(bool defaultValue) const noexcept { const auto* v = std::get_if(&this->value); if (v) { return *v; } return defaultValue; } /** * @brief Gets the string from the value or returns defaultValue * @return The string or defaultValue if this->value is not a string. */ [[nodiscard]] inline const JsonValue::String getStringOrDefault(String defaultValue) const { const auto* v = std::get_if(&this->value); if (v) { return *v; } return defaultValue; } /** * @brief Gets the double from the value or returns defaultValue * @return The double or defaultValue if this->value is not a double. */ [[nodiscard]] inline double getDoubleOrDefault(double defaultValue) const noexcept { const auto* v = std::get_if(&this->value); if (v) { return *v; } return defaultValue; } /** * @brief Gets the uint64_t from the value or returns defaultValue * @return The uint64_t or defaultValue if this->value is not a uint64_t. */ [[nodiscard]] inline std::uint64_t getUint64OrDefault(std::uint64_t defaultValue) const noexcept { const auto* v = std::get_if(&this->value); if (v) { return *v; } return defaultValue; } /** * @brief Gets the int64_t from the value or returns defaultValue * @return The int64_t or defaultValue if this->value is not a int64_t. */ [[nodiscard]] inline std::int64_t getInt64OrDefault(std::int64_t defaultValue) const noexcept { const auto* v = std::get_if(&this->value); if (v) { return *v; } return defaultValue; } /** * @brief Returns whether this value is a `null` value. */ [[nodiscard]] inline bool isNull() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is a `double`, `std::uint64_t` or * `std::int64_t`. Use this function in conjunction with `getNumber` for * safely casting to arbitrary types */ [[nodiscard]] inline bool isNumber() const noexcept { return isDouble() || isUint64() || isInt64(); } /** * @brief Returns whether this value is a `Bool` value. */ [[nodiscard]] inline bool isBool() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is a `String` value. */ [[nodiscard]] inline bool isString() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is an `Object` value. */ [[nodiscard]] inline bool isObject() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is an `Array` value. */ [[nodiscard]] inline bool isArray() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is a `double` value. */ [[nodiscard]] inline bool isDouble() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is a `std::uint64_t` value. */ [[nodiscard]] inline bool isUint64() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns whether this value is a `std::int64_t` value. */ [[nodiscard]] inline bool isInt64() const noexcept { return std::holds_alternative(this->value); } /** * @brief Returns `true` if two values are equal. */ inline bool operator==(const JsonValue& rhs) const noexcept { return this->value == rhs.value; }; /** * @brief Returns the size in bytes of this `JsonValue`. */ int64_t getSizeBytes() const noexcept { struct Operation { int64_t operator()([[maybe_unused]] const Null& /*inside*/) { return 0; } int64_t operator()([[maybe_unused]] const double& /*inside*/) { return 0; } int64_t operator()([[maybe_unused]] const std::uint64_t& /*inside*/) { return 0; } int64_t operator()([[maybe_unused]] const std::int64_t& /*inside*/) { return 0; } int64_t operator()([[maybe_unused]] const Bool& /*inside*/) { return 0; } int64_t operator()(const String& inside) { return int64_t(inside.capacity() * sizeof(char)); } int64_t operator()(const Object& inside) { int64_t accum = 0; accum += int64_t(inside.size() * (sizeof(std::string) + sizeof(JsonValue))); for (const auto& [k, v] : inside) { accum += int64_t(k.capacity() * sizeof(char) - sizeof(std::string)); accum += v.getSizeBytes() - int64_t(sizeof(JsonValue)); } return accum; } int64_t operator()(const Array& inside) { int64_t accum = 0; accum += int64_t(sizeof(JsonValue) * inside.capacity()); for (const JsonValue& v : inside) { accum += v.getSizeBytes() - int64_t(sizeof(JsonValue)); } return accum; } }; return static_cast(sizeof(JsonValue)) + std::visit(Operation{}, this->value); } /** * @brief The actual value. * * The type of the value may be queried with the `isNull`, `isDouble`, * `isBool`, `isString`, `isObject`, `isUint64`, `isInt64`, `isNumber`, and * `isArray` functions. * * The actual value may be obtained with the `getNumber`, `getBool`, * and `getString` functions for the respective types. For * `Object` values, the properties may be accessed with the * `getValueForKey` functions. */ std::variant< Null, double, std::uint64_t, std::int64_t, Bool, String, Object, Array> value; }; } // namespace CesiumUtility