mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Serialisation: Add basic utilities to facilitate conversion to/from JSON with minimal boilerplate
This commit is contained in:
parent
1bc90055b9
commit
56195d1053
48 changed files with 2057 additions and 28 deletions
459
modules/juce_core/javascript/juce_JSONSerialisation.h
Normal file
459
modules/juce_core/javascript/juce_JSONSerialisation.h
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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<int> 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<std::optional<int>> 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<T>,
|
||||
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 <typename T>
|
||||
static std::optional<var> convert (const T& t, const Options& options = {})
|
||||
{
|
||||
return Visitor::convert (t, options);
|
||||
}
|
||||
|
||||
private:
|
||||
class Visitor
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
static std::optional<var> convert (const T& t, const Options& options)
|
||||
{
|
||||
constexpr auto fallbackVersion = detail::ForwardingSerialisationTraits<T>::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<int> getVersion() const { return version; }
|
||||
|
||||
template <typename... Ts>
|
||||
void operator() (Ts&&... ts)
|
||||
{
|
||||
(visit (std::forward<Ts> (ts)), ...);
|
||||
}
|
||||
|
||||
private:
|
||||
Visitor (const std::optional<int>& explicitVersion, bool includeVersion)
|
||||
: version (explicitVersion),
|
||||
value ([&]() -> var
|
||||
{
|
||||
if (! (version.has_value() && includeVersion))
|
||||
return var();
|
||||
|
||||
auto obj = std::make_unique<DynamicObject>();
|
||||
obj->setProperty ("__version__", *version);
|
||||
return obj.release();
|
||||
}()),
|
||||
versionIncluded (includeVersion) {}
|
||||
|
||||
template <typename T>
|
||||
void visit (const T& t)
|
||||
{
|
||||
if constexpr (std::is_integral_v<T>)
|
||||
{
|
||||
push ((int64) t);
|
||||
}
|
||||
else if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
push ((double) t);
|
||||
}
|
||||
else if (auto converted = convert(t))
|
||||
{
|
||||
push (*converted);
|
||||
}
|
||||
else
|
||||
{
|
||||
value.reset();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void visit (const Named<T>& 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 <typename T>
|
||||
void visit (const SerialisationSize<T>&)
|
||||
{
|
||||
push (Array<var>{});
|
||||
}
|
||||
|
||||
void visit (const bool& t)
|
||||
{
|
||||
push (t);
|
||||
}
|
||||
|
||||
void visit (const String& t)
|
||||
{
|
||||
push (t);
|
||||
}
|
||||
|
||||
void visit (const var& t)
|
||||
{
|
||||
push (t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<var> 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 <typename T>
|
||||
bool trySetProperty (DynamicObject& obj, const Named<T>& n)
|
||||
{
|
||||
if (const auto converted = convert (n.value))
|
||||
{
|
||||
obj.setProperty (Identifier (std::string (n.name)), *converted);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<int> version;
|
||||
std::optional<var> 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<T>,
|
||||
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 <typename T>
|
||||
static std::optional<T> convert (const var& v)
|
||||
{
|
||||
return Visitor::convert<T> (v);
|
||||
}
|
||||
|
||||
private:
|
||||
class Visitor
|
||||
{
|
||||
public:
|
||||
template <typename T>
|
||||
static std::optional<T> convert (const var& v)
|
||||
{
|
||||
const auto version = [&]() -> std::optional<int>
|
||||
{
|
||||
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<T> (std::move (t))
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> getVersion() const { return version; }
|
||||
|
||||
template <typename... Ts>
|
||||
void operator() (Ts&&... ts)
|
||||
{
|
||||
(visit (std::forward<Ts> (ts)), ...);
|
||||
}
|
||||
|
||||
private:
|
||||
Visitor (std::optional<int> vn, const var& i)
|
||||
: version (vn), input (i) {}
|
||||
|
||||
template <typename T>
|
||||
void visit (T& t)
|
||||
{
|
||||
if constexpr (std::is_integral_v<T>)
|
||||
{
|
||||
readPrimitive (std::in_place_type<int64>, t);
|
||||
}
|
||||
else if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
readPrimitive (std::in_place_type<double>, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto node = getNodeToRead();
|
||||
|
||||
if (! node.has_value())
|
||||
return;
|
||||
|
||||
auto converted = convert<T> (*node);
|
||||
|
||||
if (converted.has_value())
|
||||
t = *converted;
|
||||
else
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void visit (const Named<T>& named)
|
||||
{
|
||||
auto node = getNodeToRead();
|
||||
|
||||
if (! node.has_value())
|
||||
return;
|
||||
|
||||
auto* obj = node->getDynamicObject();
|
||||
|
||||
failed = obj == nullptr || ! tryGetProperty (*obj, named);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void visit (const SerialisationSize<T>& t)
|
||||
{
|
||||
if (failed)
|
||||
return;
|
||||
|
||||
if (auto* array = input.getArray())
|
||||
{
|
||||
t.size = static_cast<T> (array->size());
|
||||
currentArrayIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void visit (bool& t)
|
||||
{
|
||||
readPrimitive (std::in_place_type<bool>, t);
|
||||
}
|
||||
|
||||
void visit (String& t)
|
||||
{
|
||||
readPrimitive (std::in_place_type<String>, t);
|
||||
}
|
||||
|
||||
void visit (var& t)
|
||||
{
|
||||
t = input;
|
||||
}
|
||||
|
||||
static std::optional<double> pullTyped (std::in_place_type_t<double>, const var& source)
|
||||
{
|
||||
return source.isDouble() ? std::optional<double> ((double) source) : std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<int64> pullTyped (std::in_place_type_t<int64>, const var& source)
|
||||
{
|
||||
return source.isInt() || source.isInt64() ? std::optional<int64> ((int64) source) : std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<bool> pullTyped (std::in_place_type_t<bool>, const var& source)
|
||||
{
|
||||
return source.isBool() || source.isInt() || source.isInt64() ? std::optional<bool> ((bool) source) : std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<String> pullTyped (std::in_place_type_t<String>, const var& source)
|
||||
{
|
||||
return source.isString() ? std::optional<String> (source.toString()) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<var> getNodeToRead()
|
||||
{
|
||||
if (failed)
|
||||
return std::nullopt;
|
||||
|
||||
if (currentArrayIndex == std::numeric_limits<size_t>::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 <typename TypeToRead, typename T>
|
||||
void readPrimitive (std::in_place_type_t<TypeToRead> tag, T& t)
|
||||
{
|
||||
auto node = getNodeToRead();
|
||||
|
||||
if (! node.has_value())
|
||||
return;
|
||||
|
||||
auto typed = pullTyped (tag, *node);
|
||||
|
||||
if (typed.has_value())
|
||||
t = static_cast<T> (*typed);
|
||||
else
|
||||
failed = true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool tryGetProperty (const DynamicObject& obj, const Named<T>& n)
|
||||
{
|
||||
const Identifier identifier (String (n.name.data(), n.name.size()));
|
||||
|
||||
if (! obj.hasProperty (identifier))
|
||||
return false;
|
||||
|
||||
const auto converted = convert<T> (obj.getProperty (identifier));
|
||||
|
||||
if (! converted.has_value())
|
||||
return false;
|
||||
|
||||
n.value = *converted;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<int> version;
|
||||
var input;
|
||||
size_t currentArrayIndex = std::numeric_limits<size_t>::max();
|
||||
bool failed = false;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
652
modules/juce_core/javascript/juce_JSONSerialisation_test.cpp
Normal file
652
modules/juce_core/javascript/juce_JSONSerialisation_test.cpp
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
struct TypeWithExternalUnifiedSerialisation
|
||||
{
|
||||
int a;
|
||||
std::string b;
|
||||
std::vector<int> c;
|
||||
std::map<std::string, int> d;
|
||||
|
||||
auto operator== (const TypeWithExternalUnifiedSerialisation& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.a, x.b, x.c, x.d); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
auto operator!= (const TypeWithExternalUnifiedSerialisation& other) const { return ! operator== (other); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<TypeWithExternalUnifiedSerialisation>
|
||||
{
|
||||
static constexpr auto marshallingVersion = 2;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("a", t.a),
|
||||
named ("b", t.b),
|
||||
named ("c", t.c),
|
||||
named ("d", t.d));
|
||||
}
|
||||
};
|
||||
|
||||
// Now that the serialiser trait is visible, it should be detected
|
||||
static_assert (detail::serialisationKind<TypeWithExternalUnifiedSerialisation> == detail::SerialisationKind::external);
|
||||
|
||||
struct TypeWithInternalUnifiedSerialisation
|
||||
{
|
||||
double a;
|
||||
float b;
|
||||
String c;
|
||||
StringArray d;
|
||||
|
||||
auto operator== (const TypeWithInternalUnifiedSerialisation& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.a, x.b, x.c, x.d); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
auto operator!= (const TypeWithInternalUnifiedSerialisation& other) const { return ! operator== (other); }
|
||||
|
||||
static constexpr auto marshallingVersion = 5;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("a", t.a),
|
||||
named ("b", t.b),
|
||||
named ("c", t.c),
|
||||
named ("d", t.d));
|
||||
}
|
||||
};
|
||||
|
||||
static_assert (detail::serialisationKind<TypeWithInternalUnifiedSerialisation> == detail::SerialisationKind::internal);
|
||||
|
||||
struct TypeWithExternalSplitSerialisation
|
||||
{
|
||||
std::optional<String> a;
|
||||
Array<int> b;
|
||||
|
||||
auto operator== (const TypeWithExternalSplitSerialisation& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.a, x.b); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
auto operator!= (const TypeWithExternalSplitSerialisation& other) const { return ! operator== (other); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<TypeWithExternalSplitSerialisation>
|
||||
{
|
||||
static constexpr auto marshallingVersion = 10;
|
||||
|
||||
template <typename Archive>
|
||||
static void load (Archive& archive, TypeWithExternalSplitSerialisation& t)
|
||||
{
|
||||
std::optional<String> a;
|
||||
Array<String> hexStrings;
|
||||
archive (named ("a", a), named ("b", hexStrings));
|
||||
|
||||
Array<int> b;
|
||||
|
||||
for (auto& i : hexStrings)
|
||||
b.add (i.getHexValue32());
|
||||
|
||||
t = { a, b };
|
||||
}
|
||||
|
||||
template <typename Archive>
|
||||
static void save (Archive& archive, const TypeWithExternalSplitSerialisation& t)
|
||||
{
|
||||
Array<String> hexStrings;
|
||||
|
||||
for (auto& i : t.b)
|
||||
hexStrings.add ("0x" + String::toHexString (i));
|
||||
|
||||
archive (named ("a", t.a), named ("b", hexStrings));
|
||||
}
|
||||
};
|
||||
|
||||
// Now that the serialiser trait is visible, it should be detected
|
||||
static_assert (detail::serialisationKind<TypeWithExternalSplitSerialisation> == detail::SerialisationKind::external);
|
||||
|
||||
// Check that serialisation kinds are correctly detected for primitives
|
||||
static_assert (detail::serialisationKind<bool> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind< int8_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind< uint8_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind< int16_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<uint16_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind< int32_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<uint32_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind< int64_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<uint64_t> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<float> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<double> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<std::byte> == detail::SerialisationKind::primitive);
|
||||
static_assert (detail::serialisationKind<String> == detail::SerialisationKind::primitive);
|
||||
|
||||
// Check that serialisation is disabled for types with no serialsation defined
|
||||
static_assert (detail::serialisationKind<Logger> == detail::SerialisationKind::none);
|
||||
static_assert (detail::serialisationKind<CriticalSection> == detail::SerialisationKind::none);
|
||||
|
||||
struct TypeWithInternalSplitSerialisation
|
||||
{
|
||||
std::string a;
|
||||
Array<int> b;
|
||||
|
||||
auto operator== (const TypeWithInternalSplitSerialisation& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.a, x.b); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
auto operator!= (const TypeWithInternalSplitSerialisation& other) const { return ! operator== (other); }
|
||||
|
||||
static constexpr auto marshallingVersion = 1;
|
||||
|
||||
template <typename Archive>
|
||||
static void load (Archive& archive, TypeWithInternalSplitSerialisation& t)
|
||||
{
|
||||
std::string a;
|
||||
Array<String> hexStrings;
|
||||
archive (named ("a", a), named ("b", hexStrings));
|
||||
|
||||
Array<int> b;
|
||||
|
||||
for (auto& i : hexStrings)
|
||||
b.add (i.getHexValue32());
|
||||
|
||||
t = { a, b };
|
||||
}
|
||||
|
||||
template <typename Archive>
|
||||
static void save (Archive& archive, const TypeWithInternalSplitSerialisation& t)
|
||||
{
|
||||
Array<String> hexStrings;
|
||||
|
||||
for (auto& i : t.b)
|
||||
hexStrings.add ("0x" + String::toHexString (i));
|
||||
|
||||
archive (named ("a", t.a), named ("b", hexStrings));
|
||||
}
|
||||
};
|
||||
|
||||
static_assert (detail::serialisationKind<TypeWithInternalSplitSerialisation> == detail::SerialisationKind::internal);
|
||||
|
||||
struct TypeWithBrokenObjectSerialisation
|
||||
{
|
||||
int a;
|
||||
int b;
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
// Archiving a named value will start reading/writing an object
|
||||
archive (named ("a", t.a));
|
||||
// Archiving a non-named value will assume that the current node is convertible
|
||||
archive (t.b);
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithBrokenPrimitiveSerialisation
|
||||
{
|
||||
int a;
|
||||
int b;
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
// Archiving a non-named value will assume that the current node is convertible
|
||||
archive (t.a);
|
||||
// Archiving a named value will fail if the current node holds a non-object type
|
||||
archive (named ("b", t.b));
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithBrokenArraySerialisation
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T&)
|
||||
{
|
||||
size_t size = 5;
|
||||
archive (size);
|
||||
|
||||
// serialisationSize should always be serialised first!
|
||||
archive (serialisationSize (size));
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithBrokenNestedSerialisation
|
||||
{
|
||||
int a;
|
||||
TypeWithBrokenObjectSerialisation b;
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("a", t.a), named ("b", t.b));
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithBrokenDynamicSerialisation
|
||||
{
|
||||
std::vector<TypeWithBrokenObjectSerialisation> a;
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (t.a);
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithVersionedSerialisation
|
||||
{
|
||||
int a{}, b{}, c{}, d{};
|
||||
|
||||
bool operator== (const TypeWithVersionedSerialisation& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.a, x.b, x.c, x.d); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const TypeWithVersionedSerialisation& other) const { return ! operator== (other); }
|
||||
|
||||
static constexpr auto marshallingVersion = 3;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("a", t.a));
|
||||
|
||||
if (archive.getVersion() >= 1)
|
||||
archive (named ("b", t.b));
|
||||
|
||||
if (archive.getVersion() >= 2)
|
||||
archive (named ("c", t.c));
|
||||
|
||||
if (archive.getVersion() >= 3)
|
||||
archive (named ("d", t.d));
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithRawVarLast
|
||||
{
|
||||
int status = 0;
|
||||
String message;
|
||||
var extended;
|
||||
|
||||
bool operator== (const TypeWithRawVarLast& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.status, x.message, x.extended); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const TypeWithRawVarLast& other) const { return ! operator== (other); }
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("status", t.status),
|
||||
named ("message", t.message),
|
||||
named ("extended", t.extended));
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithRawVarFirst
|
||||
{
|
||||
int status = 0;
|
||||
String message;
|
||||
var extended;
|
||||
|
||||
bool operator== (const TypeWithRawVarFirst& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.status, x.message, x.extended); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const TypeWithRawVarFirst& other) const { return ! operator== (other); }
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("extended", t.extended),
|
||||
named ("status", t.status),
|
||||
named ("message", t.message));
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeWithInnerVar
|
||||
{
|
||||
int eventId = 0;
|
||||
var payload;
|
||||
|
||||
bool operator== (const TypeWithInnerVar& other) const
|
||||
{
|
||||
const auto tie = [] (const auto& x) { return std::tie (x.eventId, x.payload); };
|
||||
return tie (*this) == tie (other);
|
||||
}
|
||||
|
||||
bool operator!= (const TypeWithInnerVar& other) const { return ! operator== (other); }
|
||||
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("eventId", t.eventId),
|
||||
named ("payload", t.payload));
|
||||
}
|
||||
};
|
||||
|
||||
class JSONSerialisationTest : public UnitTest
|
||||
{
|
||||
public:
|
||||
JSONSerialisationTest() : UnitTest ("JSONSerialisation", UnitTestCategories::json) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("ToVar");
|
||||
{
|
||||
expectDeepEqual (ToVar::convert (false), false);
|
||||
expectDeepEqual (ToVar::convert (true), true);
|
||||
expectDeepEqual (ToVar::convert (1), 1);
|
||||
expectDeepEqual (ToVar::convert (5.0f), 5.0);
|
||||
expectDeepEqual (ToVar::convert (6LL), 6);
|
||||
expectDeepEqual (ToVar::convert ("hello world"), "hello world");
|
||||
expectDeepEqual (ToVar::convert (String ("hello world")), "hello world");
|
||||
expectDeepEqual (ToVar::convert (std::vector<int> { 1, 2, 3 }), Array<var> { 1, 2, 3 });
|
||||
expectDeepEqual (ToVar::convert (TypeWithExternalUnifiedSerialisation { 7,
|
||||
"hello world",
|
||||
{ 5, 6, 7 },
|
||||
{ { "foo", 4 }, { "bar", 5 } } }),
|
||||
makeObject ({ { "__version__", 2 },
|
||||
{ "a", 7 },
|
||||
{ "b", "hello world" },
|
||||
{ "c", Array<var> { 5, 6, 7 } },
|
||||
{ "d", Array<var> { makeObject ({ { "first", "bar" }, { "second", 5 } }),
|
||||
makeObject ({ { "first", "foo" }, { "second", 4 } }) } } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithInternalUnifiedSerialisation { 7.89,
|
||||
4.321f,
|
||||
"custom string",
|
||||
{ "foo", "bar", "baz" } }),
|
||||
makeObject ({ { "__version__", 5 },
|
||||
{ "a", 7.89 },
|
||||
{ "b", 4.321f },
|
||||
{ "c", "custom string" },
|
||||
{ "d", Array<var> { "foo", "bar", "baz" } } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithExternalSplitSerialisation { "string", { 1, 2, 3 } }),
|
||||
makeObject ({ { "__version__", 10 },
|
||||
{ "a", makeObject ({ { "engaged", true }, { "value", "string" } }) },
|
||||
{ "b", Array<var> { "0x1", "0x2", "0x3" } } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithInternalSplitSerialisation { "string", { 16, 32, 48 } }),
|
||||
makeObject ({ { "__version__", 1 },
|
||||
{ "a", "string" },
|
||||
{ "b", Array<var> { "0x10", "0x20", "0x30" } } }));
|
||||
|
||||
expect (ToVar::convert (TypeWithBrokenObjectSerialisation { 1, 2 }) == std::nullopt);
|
||||
expect (ToVar::convert (TypeWithBrokenPrimitiveSerialisation { 1, 2 }) == std::nullopt);
|
||||
expect (ToVar::convert (TypeWithBrokenArraySerialisation {}) == std::nullopt);
|
||||
expect (ToVar::convert (TypeWithBrokenNestedSerialisation {}) == std::nullopt);
|
||||
expect (ToVar::convert (TypeWithBrokenDynamicSerialisation { std::vector<TypeWithBrokenObjectSerialisation> (10) }) == std::nullopt);
|
||||
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }),
|
||||
makeObject ({ { "__version__", 3 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 },
|
||||
{ "d", 4 } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options{}.withVersionIncluded (false)),
|
||||
makeObject ({ { "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 },
|
||||
{ "d", 4 } }));
|
||||
// Requested explicit version is higher than the type's declared version
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options{}.withExplicitVersion (4)),
|
||||
std::nullopt);
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (3)),
|
||||
makeObject ({ { "__version__", 3 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 },
|
||||
{ "d", 4 } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (2)),
|
||||
makeObject ({ { "__version__", 2 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (1)),
|
||||
makeObject ({ { "__version__", 1 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (0)),
|
||||
makeObject ({ { "__version__", 0 },
|
||||
{ "a", 1 } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (std::nullopt)),
|
||||
makeObject ({ { "a", 1 } }));
|
||||
|
||||
expectDeepEqual (ToVar::convert (TypeWithRawVarLast { 200, "success", true }),
|
||||
makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithRawVarLast { 200,
|
||||
"success",
|
||||
makeObject ({ { "status", 123.456 },
|
||||
{ "message", "failure" },
|
||||
{ "extended", true } }) }),
|
||||
makeObject ({ { "status", 200 },
|
||||
{ "message", "success" },
|
||||
{ "extended", makeObject ({ { "status", 123.456 },
|
||||
{ "message", "failure" },
|
||||
{ "extended", true } }) } }));
|
||||
|
||||
expectDeepEqual (ToVar::convert (TypeWithRawVarFirst { 200, "success", true }),
|
||||
makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
|
||||
expectDeepEqual (ToVar::convert (TypeWithRawVarFirst { 200,
|
||||
"success",
|
||||
makeObject ({ { "status", 123.456 },
|
||||
{ "message", "failure" },
|
||||
{ "extended", true } }) }),
|
||||
makeObject ({ { "status", 200 },
|
||||
{ "message", "success" },
|
||||
{ "extended", makeObject ({ { "status", 123.456 },
|
||||
{ "message", "failure" },
|
||||
{ "extended", true } }) } }));
|
||||
|
||||
const auto payload = makeObject ({ { "foo", 1 }, { "bar", 2 } });
|
||||
expectDeepEqual (ToVar::convert (TypeWithInnerVar { 404, payload }),
|
||||
makeObject ({ { "eventId", 404 }, { "payload", payload } }));
|
||||
}
|
||||
|
||||
beginTest ("FromVar");
|
||||
{
|
||||
expect (FromVar::convert<bool> (JSON::fromString ("false")) == false);
|
||||
expect (FromVar::convert<bool> (JSON::fromString ("true")) == true);
|
||||
expect (FromVar::convert<bool> (JSON::fromString ("0")) == false);
|
||||
expect (FromVar::convert<bool> (JSON::fromString ("1")) == true);
|
||||
expect (FromVar::convert<int> (JSON::fromString ("1")) == 1);
|
||||
expect (FromVar::convert<float> (JSON::fromString ("5.0f")) == 5.0f);
|
||||
expect (FromVar::convert<int64> (JSON::fromString ("6")) == 6);
|
||||
expect (FromVar::convert<String> (JSON::fromString ("\"hello world\"")) == "hello world");
|
||||
expect (FromVar::convert<std::vector<int>> (JSON::fromString ("[1,2,3]")) == std::vector<int> { 1, 2, 3 });
|
||||
expect (FromVar::convert<TypeWithExternalUnifiedSerialisation> (makeObject ({ { "__version__", 2 },
|
||||
{ "a", 7 },
|
||||
{ "b", "hello world" },
|
||||
{ "c", Array<var> { 5, 6, 7 } },
|
||||
{ "d", Array<var> { makeObject ({ { "first", "bar" }, { "second", 5 } }),
|
||||
makeObject ({ { "first", "foo" }, { "second", 4 } }) } } }))
|
||||
== TypeWithExternalUnifiedSerialisation { 7,
|
||||
"hello world",
|
||||
{ 5, 6, 7 },
|
||||
{ { "foo", 4 }, { "bar", 5 } } });
|
||||
|
||||
expect (FromVar::convert<TypeWithInternalUnifiedSerialisation> (makeObject ({ { "__version__", 5 },
|
||||
{ "a", 7.89 },
|
||||
{ "b", 4.321f },
|
||||
{ "c", "custom string" },
|
||||
{ "d", Array<var> { "foo", "bar", "baz" } } }))
|
||||
== TypeWithInternalUnifiedSerialisation { 7.89,
|
||||
4.321f,
|
||||
"custom string",
|
||||
{ "foo", "bar", "baz" } });
|
||||
|
||||
expect (FromVar::convert<TypeWithExternalSplitSerialisation> (makeObject ({ { "__version__", 10 },
|
||||
{ "a", makeObject ({ { "engaged", true }, { "value", "string" } }) },
|
||||
{ "b", Array<var> { "0x1", "0x2", "0x3" } } }))
|
||||
== TypeWithExternalSplitSerialisation { "string", { 1, 2, 3 } });
|
||||
expect (FromVar::convert<TypeWithInternalSplitSerialisation> (makeObject ({ { "__version__", 1 },
|
||||
{ "a", "string" },
|
||||
{ "b", Array<var> { "0x10", "0x20", "0x30" } } }))
|
||||
== TypeWithInternalSplitSerialisation { "string", { 16, 32, 48 } });
|
||||
|
||||
expect (FromVar::convert<TypeWithBrokenObjectSerialisation> (JSON::fromString ("null")) == std::nullopt);
|
||||
expect (FromVar::convert<TypeWithBrokenPrimitiveSerialisation> (JSON::fromString ("null")) == std::nullopt);
|
||||
expect (FromVar::convert<TypeWithBrokenArraySerialisation> (JSON::fromString ("null")) == std::nullopt);
|
||||
expect (FromVar::convert<TypeWithBrokenNestedSerialisation> (JSON::fromString ("null")) == std::nullopt);
|
||||
expect (FromVar::convert<TypeWithBrokenDynamicSerialisation> (JSON::fromString ("null")) == std::nullopt);
|
||||
|
||||
expect (FromVar::convert<TypeWithInternalUnifiedSerialisation> (makeObject ({ { "a", 7.89 },
|
||||
{ "b", 4.321f } })) == std::nullopt);
|
||||
|
||||
expect (FromVar::convert<TypeWithVersionedSerialisation> (makeObject ({ { "__version__", 3 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 },
|
||||
{ "d", 4 } }))
|
||||
== TypeWithVersionedSerialisation { 1, 2, 3, 4 });
|
||||
expect (FromVar::convert<TypeWithVersionedSerialisation> (makeObject ({ { "__version__", 4 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 },
|
||||
{ "d", 4 } }))
|
||||
== TypeWithVersionedSerialisation { 1, 2, 3, 4 });
|
||||
expect (FromVar::convert<TypeWithVersionedSerialisation> (makeObject ({ { "__version__", 2 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 },
|
||||
{ "c", 3 } }))
|
||||
== TypeWithVersionedSerialisation { 1, 2, 3, 0 });
|
||||
expect (FromVar::convert<TypeWithVersionedSerialisation> (makeObject ({ { "__version__", 1 },
|
||||
{ "a", 1 },
|
||||
{ "b", 2 } }))
|
||||
== TypeWithVersionedSerialisation { 1, 2, 0, 0 });
|
||||
expect (FromVar::convert<TypeWithVersionedSerialisation> (makeObject ({ { "__version__", 0 },
|
||||
{ "a", 1 } }))
|
||||
== TypeWithVersionedSerialisation { 1, 0, 0, 0 });
|
||||
expect (FromVar::convert<TypeWithVersionedSerialisation> (makeObject ({ { "a", 1 } }))
|
||||
== TypeWithVersionedSerialisation { 1, 0, 0, 0 });
|
||||
|
||||
const auto raw = makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", "another string" } });
|
||||
expect (FromVar::convert<TypeWithRawVarLast> (raw) == TypeWithRawVarLast { 200, "success", "another string" });
|
||||
expect (FromVar::convert<TypeWithRawVarFirst> (raw) == TypeWithRawVarFirst { 200, "success", "another string" });
|
||||
|
||||
const var payloads[] { makeObject ({ { "foo", 1 }, { "bar", 2 } }),
|
||||
var (Array<var> { 1, 2 }),
|
||||
var() };
|
||||
|
||||
for (const auto& payload : payloads)
|
||||
{
|
||||
const auto objectWithPayload = makeObject ({ { "eventId", 404 }, { "payload", payload } });
|
||||
expect (FromVar::convert<TypeWithInnerVar> (objectWithPayload) == TypeWithInnerVar { 404, payload });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void expectDeepEqual (const std::optional<var>& a, const std::optional<var>& b)
|
||||
{
|
||||
expect (deepEqual (a, b), a.has_value() && b.has_value() ? JSON::toString (*a) + " != " + JSON::toString (*b) : String());
|
||||
}
|
||||
|
||||
static var makeObject (const std::map<Identifier, var>& map)
|
||||
{
|
||||
auto obj = std::make_unique<DynamicObject>();
|
||||
|
||||
for (auto& [key, value] : map)
|
||||
obj->setProperty (key, value);
|
||||
|
||||
return obj.release();
|
||||
}
|
||||
|
||||
static bool deepEqual (const DynamicObject& a, const DynamicObject& b)
|
||||
{
|
||||
if (a.getProperties().size() != b.getProperties().size())
|
||||
return false;
|
||||
|
||||
for (const auto& [key, value] : a.getProperties())
|
||||
{
|
||||
if (! b.hasProperty (key))
|
||||
return false;
|
||||
|
||||
if (! deepEqual (value, b.getProperty (key)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool deepEqual (const Array<var>& a, const Array<var>& b)
|
||||
{
|
||||
return std::equal (a.begin(), a.end(), b.begin(), b.end(), [] (const var& i, const var& j) { return deepEqual (i, j); });
|
||||
}
|
||||
|
||||
static bool deepEqual (const var& a, const var& b)
|
||||
{
|
||||
if (auto* i = a.getDynamicObject())
|
||||
if (auto* j = b.getDynamicObject())
|
||||
return deepEqual (*i, *j);
|
||||
|
||||
if (auto* i = a.getArray())
|
||||
if (auto* j = b.getArray())
|
||||
return deepEqual (*i, *j);
|
||||
|
||||
return a == b;
|
||||
}
|
||||
|
||||
static bool deepEqual (const std::optional<var>& a, const std::optional<var>& b)
|
||||
{
|
||||
if (a.has_value() != b.has_value())
|
||||
return false;
|
||||
|
||||
return ! a.has_value() || deepEqual (*a, *b);
|
||||
}
|
||||
};
|
||||
|
||||
static JSONSerialisationTest jsonSerialisationTest;
|
||||
|
||||
} // namespace juce
|
||||
|
|
@ -281,6 +281,7 @@
|
|||
#include "maths/juce_MathsFunctions_test.cpp"
|
||||
#include "misc/juce_EnumHelpers_test.cpp"
|
||||
#include "containers/juce_FixedSizeFunction_test.cpp"
|
||||
#include "javascript/juce_JSONSerialisation_test.cpp"
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -309,6 +309,8 @@ JUCE_END_IGNORE_WARNINGS_MSVC
|
|||
#include "streams/juce_FileInputSource.h"
|
||||
#include "logging/juce_FileLogger.h"
|
||||
#include "javascript/juce_JSON.h"
|
||||
#include "serialisation/juce_Serialisation.h"
|
||||
#include "javascript/juce_JSONSerialisation.h"
|
||||
#include "javascript/juce_Javascript.h"
|
||||
#include "maths/juce_BigInteger.h"
|
||||
#include "maths/juce_Expression.h"
|
||||
|
|
|
|||
576
modules/juce_core/serialisation/juce_Serialisation.h
Normal file
576
modules/juce_core/serialisation/juce_Serialisation.h
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
#define JUCE_COMPARISON_OPS X(==) X(!=) X(<) X(<=) X(>) X(>=)
|
||||
|
||||
/**
|
||||
Combines an object with a name.
|
||||
|
||||
Instances of Named have reference-like semantics. That is, Named stores a reference
|
||||
to a wrapped value, rather than storing the value internally.
|
||||
|
||||
@tparam T the type of reference that is wrapped. Passing "const T" will cause the Named
|
||||
instance to hold a "const T&"; passing "T" will cause the Named instance to
|
||||
hold a "T&".
|
||||
|
||||
@see named()
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
template <typename T>
|
||||
struct Named
|
||||
{
|
||||
#define X(op) auto operator op (const Named& other) const { return value op other.value; }
|
||||
JUCE_COMPARISON_OPS
|
||||
#undef X
|
||||
|
||||
std::string_view name; ///< A name that corresponds to the value
|
||||
T& value; ///< A reference to a value to wrap
|
||||
};
|
||||
|
||||
/** Produces a Named instance that holds a mutable reference. */
|
||||
template <typename T> constexpr auto named (std::string_view c, T& t) { return Named<T> { c, t }; }
|
||||
|
||||
/** Produces a Named instance that holds an immutable reference. */
|
||||
template <typename T> constexpr auto named (std::string_view c, const T& t) { return Named<const T> { c, t }; }
|
||||
|
||||
/**
|
||||
Holds a reference to some kind of size value, used to indicate that an object being marshalled
|
||||
is of variable size (e.g. Array, vector, map, set, etc.).
|
||||
|
||||
If you need to write your own serialisation routines for a dynamically-sized type, ensure
|
||||
that you archive an instance of SerialisationSize before any of the contents of the container.
|
||||
|
||||
@tparam the (probably numeric) type of the size value
|
||||
|
||||
@see serialisztionSize()
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
template <typename T>
|
||||
struct SerialisationSize
|
||||
{
|
||||
#define X(op) auto operator op (const SerialisationSize& other) const { return size op other.size; }
|
||||
JUCE_COMPARISON_OPS
|
||||
#undef X
|
||||
|
||||
T& size;
|
||||
};
|
||||
|
||||
/** Produces a SerialisationSize instance that holds a mutable reference to a size value. */
|
||||
template <typename T> constexpr auto serialisationSize (T& t) -> std::enable_if_t<std::is_integral_v<T>, SerialisationSize<T>> { return { t }; }
|
||||
|
||||
/** Produces a SerialisationSize instance that holds an immutable reference to a size value. */
|
||||
template <typename T> constexpr auto serialisationSize (const T& t) -> std::enable_if_t<std::is_integral_v<T>, SerialisationSize<const T>> { return { t }; }
|
||||
|
||||
#undef JUCE_COMPARISON_OPS
|
||||
|
||||
/**
|
||||
Allows serialisation functions to be attached to a specific type without having to modify the
|
||||
declaration of that type.
|
||||
|
||||
A specialisation of SerialisationTraits must include:
|
||||
- A static constexpr data member named 'marshallingVersion' with a value that is convertible
|
||||
to std::optional<int>.
|
||||
- Either:
|
||||
- Normally, a single function with the following signature:
|
||||
@code
|
||||
template <typename Archive, typename Item>
|
||||
static void serialise (Archive& archive, Item& item);
|
||||
@endcode
|
||||
- For types that must do slightly different work when loading and saving, you may supply two
|
||||
functions with the following signatures, where "T" is a placeholder for the type on which
|
||||
SerialisationTraits is specialised:
|
||||
@code
|
||||
template <typename Archive>
|
||||
static void load (Archive& archive, T& item);
|
||||
|
||||
template <typename Archive>
|
||||
static void save (Archive& archive, const T& item);
|
||||
@endcode
|
||||
|
||||
If the marshallingVersion converts to a null optional, then all versioning information will be
|
||||
ignored when marshalling the type. Otherwise, if the value converts to a non-null optional, this
|
||||
versioning information will be included when serialising the type.
|
||||
|
||||
Inside serialise() and load() you may call archive.getVersion() to find the detected version
|
||||
of the object being deserialised. archive.getVersion() will return an std::optional<int>,
|
||||
where 'nullopt' indicates that no versioning information was detected.
|
||||
|
||||
Marshalling functions can also be specified directly inside the type to be marshalled. This
|
||||
approach may be preferable as it is more concise. Internal marshalling functions are written
|
||||
in exactly the same way as external ones; i.e. the type must include a marshallingVersion,
|
||||
and either a single serialise function, or a load/save pair of functions, as specified above.
|
||||
|
||||
@tags{Core}
|
||||
*/
|
||||
template <typename> struct SerialisationTraits
|
||||
{
|
||||
/* Intentionally left blank. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/*
|
||||
The following are specialisations of SerialisationTraits for commonly-used types.
|
||||
*/
|
||||
|
||||
template <typename... Ts>
|
||||
struct SerialisationTraits<std::vector<Ts...>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void load (Archive& archive, T& t)
|
||||
{
|
||||
auto size = t.size();
|
||||
archive (serialisationSize (size));
|
||||
t.resize (size);
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void save (Archive& archive, const T& t)
|
||||
{
|
||||
archive (serialisationSize (t.size()));
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Element, typename Mutex, int minSize>
|
||||
struct SerialisationTraits<Array<Element, Mutex, minSize>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void load (Archive& archive, T& t)
|
||||
{
|
||||
auto size = t.size();
|
||||
archive (serialisationSize (size));
|
||||
t.resize (size);
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void save (Archive& archive, const T& t)
|
||||
{
|
||||
archive (serialisationSize (t.size()));
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<StringArray>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (t.strings);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct SerialisationTraits<std::pair<Ts...>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t)
|
||||
{
|
||||
archive (named ("first", t.first), named ("second", t.second));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct SerialisationTraits<std::optional<T>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive>
|
||||
static void load (Archive& archive, std::optional<T>& t)
|
||||
{
|
||||
bool engaged = false;
|
||||
|
||||
archive (named ("engaged", engaged));
|
||||
|
||||
if (! engaged)
|
||||
return;
|
||||
|
||||
t.emplace();
|
||||
archive (named ("value", *t));
|
||||
}
|
||||
|
||||
template <typename Archive>
|
||||
static void save (Archive& archive, const std::optional<T>& t)
|
||||
{
|
||||
archive (named ("engaged", t.has_value()));
|
||||
|
||||
if (t.has_value())
|
||||
archive (named ("value", *t));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct SerialisationTraits<std::string>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive>
|
||||
static void load (Archive& archive, std::string& t)
|
||||
{
|
||||
String temporary;
|
||||
archive (temporary);
|
||||
t = temporary.toStdString();
|
||||
}
|
||||
|
||||
template <typename Archive>
|
||||
static void save (Archive& archive, const std::string& t)
|
||||
{
|
||||
archive (String (t));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct SerialisationTraits<std::map<Ts...>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void load (Archive& archive, T& t)
|
||||
{
|
||||
auto size = t.size();
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (auto i = (decltype (size)) 0; i < size; ++i)
|
||||
{
|
||||
std::pair<typename T::key_type, typename T::mapped_type> element;
|
||||
archive (element);
|
||||
t.insert (element);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void save (Archive& archive, const T& t)
|
||||
{
|
||||
auto size = t.size();
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (const auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct SerialisationTraits<std::set<Ts...>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void load (Archive& archive, T& t)
|
||||
{
|
||||
auto size = t.size();
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (auto i = (decltype (size)) 0; i < size; ++i)
|
||||
{
|
||||
typename T::value_type element;
|
||||
archive (element);
|
||||
t.insert (element);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void save (Archive& archive, const T& t)
|
||||
{
|
||||
auto size = t.size();
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (const auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct SerialisationTraits<char[N]>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void serialise (Archive& archive, T& t) { archive (String (t, N)); }
|
||||
};
|
||||
|
||||
template <typename Element, size_t N>
|
||||
struct SerialisationTraits<Element[N]>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void load (Archive& archive, T& t)
|
||||
{
|
||||
auto size = N;
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void save (Archive& archive, const T& t)
|
||||
{
|
||||
const auto size = N;
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Element, size_t N>
|
||||
struct SerialisationTraits<std::array<Element, N>>
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void load (Archive& archive, T& t)
|
||||
{
|
||||
auto size = N;
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
|
||||
template <typename Archive, typename T>
|
||||
static void save (Archive& archive, const T& t)
|
||||
{
|
||||
const auto size = N;
|
||||
archive (serialisationSize (size));
|
||||
|
||||
for (auto& element : t)
|
||||
archive (element);
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef DOXYGEN
|
||||
|
||||
/*
|
||||
This namespace holds utilities for detecting and using serialisation functions.
|
||||
|
||||
The contents of this namespace are private, and liable to change, so you shouldn't use any of
|
||||
the contents directly.
|
||||
*/
|
||||
namespace detail
|
||||
{
|
||||
struct DummyArchive
|
||||
{
|
||||
template <typename... Ts>
|
||||
bool operator() (Ts&&...);
|
||||
|
||||
std::optional<int> getVersion() const { return {}; }
|
||||
};
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto hasInternalVersion = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasInternalVersion<T, std::void_t<decltype (T::marshallingVersion)>> = true;
|
||||
|
||||
template <typename Traits, typename T, typename = void>
|
||||
constexpr auto hasInternalSerialise = false;
|
||||
|
||||
template <typename Traits, typename T>
|
||||
constexpr auto hasInternalSerialise<Traits, T, std::void_t<decltype (Traits::serialise (std::declval<DummyArchive&>(), std::declval<T&>()))>> = true;
|
||||
|
||||
template <typename Traits, typename T, typename = void>
|
||||
constexpr auto hasInternalLoad = false;
|
||||
|
||||
template <typename Traits, typename T>
|
||||
constexpr auto hasInternalLoad<Traits, T, std::void_t<decltype (Traits::load (std::declval<DummyArchive&>(), std::declval<T&>()))>> = true;
|
||||
|
||||
template <typename Traits, typename T, typename = void>
|
||||
constexpr auto hasInternalSave = false;
|
||||
|
||||
template <typename Traits, typename T>
|
||||
constexpr auto hasInternalSave<Traits, T, std::void_t<decltype (Traits::save (std::declval<DummyArchive&>(), std::declval<const T&>()))>> = true;
|
||||
|
||||
template <typename T>
|
||||
struct SerialisedTypeTrait { using type = T; };
|
||||
|
||||
template <typename T>
|
||||
struct SerialisedTypeTrait<SerialisationTraits<T>> { using type = T; };
|
||||
|
||||
template <typename T>
|
||||
using SerialisedType = typename SerialisedTypeTrait<T>::type;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasSerialisation = hasInternalVersion<SerialisedType<T>>
|
||||
|| hasInternalSerialise<T, SerialisedType<T>>
|
||||
|| hasInternalLoad<T, SerialisedType<T>>
|
||||
|| hasInternalSave<T, SerialisedType<T>>;
|
||||
|
||||
/* Different kinds of serialisation function. */
|
||||
enum class SerialisationKind
|
||||
{
|
||||
none, // The type doesn't have any serialisation
|
||||
primitive, // The type has serialisation handling defined directly on the archiver. enums will be converted to equivalent integral values
|
||||
internal, // The type has internally-defined serialisation utilities
|
||||
external, // The type has an external specialisation of SerialisationTraits
|
||||
};
|
||||
|
||||
/* The SerialisationKind to use for the type T.
|
||||
|
||||
Primitive serialisation is used for arithmetic types, enums, Strings, and vars.
|
||||
Internal serialisation is used for types that declare an internal marshallingVersion,
|
||||
serialise(), load(), or save().
|
||||
External serialisation is used in all other cases.
|
||||
*/
|
||||
template <typename T>
|
||||
constexpr auto serialisationKind = []
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<T> || std::is_enum_v<T> || std::is_same_v<T, String> || std::is_same_v<T, var>)
|
||||
return SerialisationKind::primitive;
|
||||
else if constexpr (hasSerialisation<T>)
|
||||
return SerialisationKind::internal;
|
||||
else if constexpr (hasSerialisation<SerialisationTraits<T>>)
|
||||
return SerialisationKind::external;
|
||||
else
|
||||
return SerialisationKind::none;
|
||||
}();
|
||||
|
||||
/* This trait defines the serialisation utilities that are used for primitive types. */
|
||||
template <typename T, SerialisationKind kind = serialisationKind<T>>
|
||||
struct ForwardingSerialisationTraits
|
||||
{
|
||||
static constexpr auto marshallingVersion = std::nullopt;
|
||||
|
||||
template <typename Archive, typename Primitive>
|
||||
static auto load (Archive& archive, Primitive& t)
|
||||
{
|
||||
if constexpr (std::is_enum_v<Primitive>)
|
||||
return archive (*reinterpret_cast<std::underlying_type_t<Primitive>*> (&t));
|
||||
else
|
||||
return archive (t);
|
||||
}
|
||||
|
||||
template <typename Archive, typename Primitive>
|
||||
static auto save (Archive& archive, const Primitive& t)
|
||||
{
|
||||
if constexpr (std::is_enum_v<Primitive>)
|
||||
return archive (*reinterpret_cast<const std::underlying_type_t<Primitive>*> (&t));
|
||||
else
|
||||
return archive (t);
|
||||
}
|
||||
};
|
||||
|
||||
/* This specialisation will be used for types with internal serialisation.
|
||||
|
||||
All members of ForwardingSerialisationTraits forward to the corresponding member of T.
|
||||
*/
|
||||
template <typename T>
|
||||
struct ForwardingSerialisationTraits<T, SerialisationKind::internal>
|
||||
{
|
||||
static constexpr std::optional<int> marshallingVersion { T::marshallingVersion };
|
||||
|
||||
template <typename Archive, typename Item>
|
||||
static auto serialise (Archive& archive, Item& t) -> decltype (Item::serialise (archive, t)) { return Item::serialise (archive, t); }
|
||||
|
||||
template <typename Archive, typename Item>
|
||||
static auto load (Archive& archive, Item& t) -> decltype (Item::load (archive, t)) { return Item::load (archive, t); }
|
||||
|
||||
template <typename Archive, typename Item>
|
||||
static auto save (Archive& archive, const Item& t) -> decltype (Item::save (archive, t)) { return Item::save (archive, t); }
|
||||
};
|
||||
|
||||
/* This specialisation will be used for types with external serialisation.
|
||||
|
||||
@see SerialisationTraits
|
||||
*/
|
||||
template <typename T>
|
||||
struct ForwardingSerialisationTraits<T, SerialisationKind::external> : SerialisationTraits<T> {};
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto hasSerialise = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasSerialise<T, std::void_t<decltype (ForwardingSerialisationTraits<T>::serialise (std::declval<DummyArchive&>(), std::declval<T&>()))>> = true;
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto hasLoad = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasLoad<T, std::void_t<decltype (ForwardingSerialisationTraits<T>::load (std::declval<DummyArchive&>(), std::declval<T&>()))>> = true;
|
||||
|
||||
template <typename T, typename = void>
|
||||
constexpr auto hasSave = false;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto hasSave<T, std::void_t<decltype (ForwardingSerialisationTraits<T>::save (std::declval<DummyArchive&>(), std::declval<const T&>()))>> = true;
|
||||
|
||||
template <typename T>
|
||||
constexpr auto delayStaticAssert = false;
|
||||
|
||||
/* Calls the correct function (serialise or save) to save the argument t to the archive.
|
||||
*/
|
||||
template <typename Archive, typename T>
|
||||
auto doSave (Archive& archive, const T& t)
|
||||
{
|
||||
if constexpr (serialisationKind<T> == SerialisationKind::none)
|
||||
static_assert (delayStaticAssert<T>, "No serialisation function found or marshallingVersion unset");
|
||||
else if constexpr (hasSerialise<T> && ! hasSave<T>)
|
||||
return ForwardingSerialisationTraits<T>::serialise (archive, t);
|
||||
else if constexpr (! hasSerialise<T> && hasSave<T>)
|
||||
return ForwardingSerialisationTraits<T>::save (archive, t);
|
||||
else
|
||||
static_assert (delayStaticAssert<T>, "Multiple serialisation functions found");
|
||||
}
|
||||
|
||||
/* Calls the correct function (serialise or load) to load the argument t from the archive.
|
||||
*/
|
||||
template <typename Archive, typename T>
|
||||
auto doLoad (Archive& archive, T& t)
|
||||
{
|
||||
if constexpr (serialisationKind<T> == SerialisationKind::none)
|
||||
static_assert (delayStaticAssert<T>, "No serialisation function found or marshallingVersion unset");
|
||||
else if constexpr (hasSerialise<T> && ! hasLoad<T>)
|
||||
return ForwardingSerialisationTraits<T>::serialise (archive, t);
|
||||
else if constexpr (! hasSerialise<T> && hasLoad<T>)
|
||||
return ForwardingSerialisationTraits<T>::load (archive, t);
|
||||
else
|
||||
static_assert (delayStaticAssert<T>, "Multiple serialisation functions found");
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
Loading…
Add table
Add a link
Reference in a new issue