/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. The code included in this file is provided under the terms of the ISC license http://www.isc.org/downloads/software-support-policy/isc-license. Permission To use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted provided that the above copyright notice and this permission notice appear in all copies. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { /** Options that control conversion from arbitrary types to juce::var. @see ToVar @tags{Core} */ class ToVarOptions { public: /** By default, conversion will serialise the type using the marshallingVersion defined for that type. Setting an explicit version allows the type to be serialised as an earlier version. */ [[nodiscard]] ToVarOptions withExplicitVersion (std::optional x) const { return withMember (*this, &ToVarOptions::explicitVersion, x); } /** By default, conversion will include version information for any type with a non-null marshallingVersion. Setting versionIncluded to false will cause the version info to be omitted, which is useful in situations where the version information is not needed (e.g. when presenting transient information to the user, rather than writing data to disk that must be deserialised in the future). */ [[nodiscard]] ToVarOptions withVersionIncluded (bool x) const { return withMember (*this, &ToVarOptions::versionIncluded, x); } /** @see withExplicitVersion() */ [[nodiscard]] auto getExplicitVersion() const { return explicitVersion; } /** @see withVersionIncluded(). */ [[nodiscard]] auto getVersionIncluded() const { return versionIncluded; } private: std::optional> explicitVersion; bool versionIncluded = true; }; /** Allows converting an object of arbitrary type to var. To use this, you must first ensure that the type passed to convert is set up for serialisation. For details of what this entails, see the docs for SerialisationTraits. In short, the constant 'marshallingVersion', and either the single function 'serialise()', or the function pair 'load()' and 'save()' must be defined for the type. These may be defined as public members of the type T itself, or as public members of juce::SerialisationTraits, which is a specialisation of the SerialisationTraits template struct for the type T. @see FromVar @tags{Core} */ class ToVar { public: using Options = ToVarOptions; /** Attempts to convert the argument to a var using the serialisation utilities specified for that type. This will return a non-null optional if conversion succeeds, or nullopt if conversion fails. */ template static std::optional convert (const T& t, const Options& options = {}) { return Visitor::convert (t, options); } private: class Visitor { public: template static std::optional convert (const T& t, const Options& options) { constexpr auto fallbackVersion = detail::ForwardingSerialisationTraits::marshallingVersion; const auto versionToUse = options.getExplicitVersion() .value_or (fallbackVersion); if (versionToUse > fallbackVersion) { // The requested explicit version is higher than the declared version of the type. return std::nullopt; } Visitor visitor { versionToUse, options.getVersionIncluded() }; detail::doSave (visitor, t); return visitor.value; } std::optional getVersion() const { return version; } template void operator() (Ts&&... ts) { (visit (std::forward (ts)), ...); } private: Visitor (const std::optional& explicitVersion, bool includeVersion) : version (explicitVersion), value ([&]() -> var { if (! (version.has_value() && includeVersion)) return var(); auto obj = std::make_unique(); obj->setProperty ("__version__", *version); return obj.release(); }()), versionIncluded (includeVersion) {} template void visit (const T& t) { if constexpr (std::is_integral_v) { push ((int64) t); } else if constexpr (std::is_floating_point_v) { push ((double) t); } else if (auto converted = convert (t)) { push (*converted); } else { value.reset(); } } template void visit (const Named& named) { if (! value.has_value()) return; if (value == var()) value = new DynamicObject; auto* obj = value->getDynamicObject(); if (obj == nullptr) { // Serialisation failure! This may be caused by archiving a primitive or // SerialisationSize, and then attempting to archive a named pair to the same // archive instance. // When using named pairs, *all* items serialised with a particular archiver must be // named pairs. jassertfalse; value.reset(); return; } if (! trySetProperty (*obj, named)) value.reset(); } template void visit (const SerialisationSize&) { push (Array{}); } void visit (const bool& t) { push (t); } void visit (const String& t) { push (t); } void visit (const var& t) { push (t); } template std::optional convert (const T& t) { return convert (t, Options{}.withVersionIncluded (versionIncluded)); } void push (var v) { if (! value.has_value()) return; if (*value == var()) *value = v; else if (auto* array = value->getArray()) array->add (v); else value.reset(); } template bool trySetProperty (DynamicObject& obj, const Named& n) { if (const auto converted = convert (n.value)) { obj.setProperty (Identifier (std::string (n.name)), *converted); return true; } return false; } std::optional version; std::optional value; bool versionIncluded = true; }; }; //============================================================================== /** Allows converting a var to an object of arbitrary type. To use this, you must first ensure that the type passed to convert is set up for serialisation. For details of what this entails, see the docs for SerialisationTraits. In short, the constant 'marshallingVersion', and either the single function 'serialise()', or the function pair 'load()' and 'save()' must be defined for the type. These may be defined as public members of the type T itself, or as public members of juce::SerialisationTraits, which is a specialisation of the SerialisationTraits template struct for the type T. @see ToVar @tags{Core} */ class FromVar { public: /** Attempts to convert a var to an instance of type T. This will return a non-null optional if conversion succeeds, or nullopt if conversion fails. */ template static std::optional convert (const var& v) { return Visitor::convert (v); } private: class Visitor { public: template static std::optional convert (const var& v) { const auto version = [&]() -> std::optional { if (auto* obj = v.getDynamicObject()) if (obj->hasProperty ("__version__")) return (int) obj->getProperty ("__version__"); return std::nullopt; }(); Visitor visitor { version, v }; T t{}; detail::doLoad (visitor, t); return ! visitor.failed ? std::optional (std::move (t)) : std::nullopt; } std::optional getVersion() const { return version; } template void operator() (Ts&&... ts) { (visit (std::forward (ts)), ...); } private: Visitor (std::optional vn, const var& i) : version (vn), input (i) {} template void visit (T& t) { if constexpr (std::is_integral_v) { readPrimitive (std::in_place_type, t); } else if constexpr (std::is_floating_point_v) { readPrimitive (std::in_place_type, t); } else { auto node = getNodeToRead(); if (! node.has_value()) return; auto converted = convert (*node); if (converted.has_value()) t = *converted; else failed = true; } } template void visit (const Named& named) { auto node = getNodeToRead(); if (! node.has_value()) return; auto* obj = node->getDynamicObject(); failed = obj == nullptr || ! tryGetProperty (*obj, named); } template void visit (const SerialisationSize& t) { if (failed) return; if (auto* array = input.getArray()) { t.size = static_cast (array->size()); currentArrayIndex = 0; } else { failed = true; } } void visit (bool& t) { readPrimitive (std::in_place_type, t); } void visit (String& t) { readPrimitive (std::in_place_type, t); } void visit (var& t) { t = input; } static std::optional pullTyped (std::in_place_type_t, const var& source) { return source.isDouble() ? std::optional ((double) source) : std::nullopt; } static std::optional pullTyped (std::in_place_type_t, const var& source) { return source.isInt() || source.isInt64() ? std::optional ((int64) source) : std::nullopt; } static std::optional pullTyped (std::in_place_type_t, const var& source) { return std::optional ((bool) source); } static std::optional pullTyped (std::in_place_type_t, const var& source) { return source.isString() ? std::optional (source.toString()) : std::nullopt; } std::optional getNodeToRead() { if (failed) return std::nullopt; if (currentArrayIndex == std::numeric_limits::max()) return input; const auto* array = input.getArray(); if (array == nullptr) return input; if ((int) currentArrayIndex < array->size()) return array->getReference ((int) currentArrayIndex++); failed = true; return std::nullopt; } template void readPrimitive (std::in_place_type_t tag, T& t) { auto node = getNodeToRead(); if (! node.has_value()) return; auto typed = pullTyped (tag, *node); if (typed.has_value()) t = static_cast (*typed); else failed = true; } template static bool tryGetProperty (const DynamicObject& obj, const Named& n) { const Identifier identifier (String (n.name.data(), n.name.size())); if (! obj.hasProperty (identifier)) return false; const auto converted = convert (obj.getProperty (identifier)); if (! converted.has_value()) return false; n.value = *converted; return true; } std::optional version; var input; size_t currentArrayIndex = std::numeric_limits::max(); bool failed = false; }; }; //============================================================================== /** This template-overloaded class can be used to convert between var and custom types. If not specialised, the variant converter will attempt to use serialisation functions if they are detected for the given type. For details of what this entails, see the docs for SerialisationTraits. In short, the constant 'marshallingVersion', and either the single function 'serialise()', or the function pair 'load()' and 'save()' must be defined for the type. These may be defined as public members of the type T itself, or as public members of juce::SerialisationTraits, which is a specialisation of the SerialisationTraits template struct for the type T. @see ToVar, FromVar @tags{Core} */ template struct VariantConverter { static Type fromVar (const var& v) { return static_cast (v); } static var toVar (const Type& t) { return t; } }; #ifndef DOXYGEN template <> struct VariantConverter { static String fromVar (const var& v) { return v.toString(); } static var toVar (const String& s) { return s; } }; #endif /** A helper type that can be used to implement specialisations of VariantConverter that use FromVar::convert and ToVar::convert internally. If you've already implemented SerialisationTraits for a specific type, and don't want to write a custom VariantConverter that duplicates that implementation, you can instead write: @code template <> struct juce::VariantConverter : public juce::StrictVariantConverter {}; @endcode @tags{Core} */ template struct StrictVariantConverter { static_assert (detail::serialisationKind != detail::SerialisationKind::none); static Type fromVar (const var& v) { auto converted = FromVar::convert (v); jassert (converted.has_value()); return std::move (converted).value_or (Type{}); } static var toVar (const Type& t) { auto converted = ToVar::convert<> (t); jassert (converted.has_value()); return std::move (converted).value_or (var{}); } }; } // namespace juce