Loading...
Searching...
No Matches
juce_JSONSerialisation_test.cpp
/*
==============================================================================
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 final : 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 } } }),
JSONUtils::makeObject ({ { "__version__", 2 },
{ "a", 7 },
{ "b", "hello world" },
{ "c", Array<var> { 5, 6, 7 } },
{ "d",
Array<var> { JSONUtils::makeObject ({ { "first", "bar" },
{ "second", 5 } }),
JSONUtils::makeObject ({ { "first", "foo" },
{ "second", 4 } }) } } }));
expectDeepEqual (ToVar::convert (TypeWithInternalUnifiedSerialisation { 7.89,
4.321f,
"custom string",
{ "foo", "bar", "baz" } }),
JSONUtils::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 } }),
JSONUtils::makeObject ({ { "__version__", 10 },
{ "a", JSONUtils::makeObject ({ { "engaged", true }, { "value", "string" } }) },
{ "b", Array<var> { "0x1", "0x2", "0x3" } } }));
expectDeepEqual (ToVar::convert (TypeWithInternalSplitSerialisation { "string", { 16, 32, 48 } }),
JSONUtils::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 }),
JSONUtils::makeObject ({ { "__version__", 3 },
{ "a", 1 },
{ "b", 2 },
{ "c", 3 },
{ "d", 4 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withVersionIncluded (false)),
JSONUtils::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)),
JSONUtils::makeObject ({ { "__version__", 3 },
{ "a", 1 },
{ "b", 2 },
{ "c", 3 },
{ "d", 4 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (2)),
JSONUtils::makeObject ({ { "__version__", 2 },
{ "a", 1 },
{ "b", 2 },
{ "c", 3 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (1)),
JSONUtils::makeObject ({ { "__version__", 1 },
{ "a", 1 },
{ "b", 2 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (0)),
JSONUtils::makeObject ({ { "__version__", 0 },
{ "a", 1 } }));
expectDeepEqual (ToVar::convert (TypeWithVersionedSerialisation { 1, 2, 3, 4 }, ToVar::Options {}.withExplicitVersion (std::nullopt)),
JSONUtils::makeObject ({ { "a", 1 } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarLast { 200, "success", true }),
JSONUtils::makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarLast { 200,
"success",
JSONUtils::makeObject ({ { "status", 123.456 },
{ "message", "failure" },
{ "extended", true } }) }),
JSONUtils::makeObject ({ { "status", 200 },
{ "message", "success" },
{ "extended", JSONUtils::makeObject ({ { "status", 123.456 },
{ "message", "failure" },
{ "extended", true } }) } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarFirst { 200, "success", true }),
JSONUtils::makeObject ({ { "status", 200 }, { "message", "success" }, { "extended", true } }));
expectDeepEqual (ToVar::convert (TypeWithRawVarFirst { 200,
"success",
JSONUtils::makeObject ({ { "status", 123.456 },
{ "message", "failure" },
{ "extended", true } }) }),
JSONUtils::makeObject ({ { "status", 200 },
{ "message", "success" },
{ "extended", JSONUtils::makeObject ({ { "status", 123.456 },
{ "message", "failure" },
{ "extended", true } }) } }));
const auto payload = JSONUtils::makeObject ({ { "foo", 1 }, { "bar", 2 } });
expectDeepEqual (ToVar::convert (TypeWithInnerVar { 404, payload }),
JSONUtils::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> (JSONUtils::makeObject ({ { "__version__", 2 },
{ "a", 7 },
{ "b", "hello world" },
{ "c", Array<var> { 5, 6, 7 } },
{ "d",
Array<var> { JSONUtils::makeObject ({ { "first", "bar" },
{ "second", 5 } }),
JSONUtils::makeObject ({ { "first", "foo" },
{ "second", 4 } }) } } }))
== TypeWithExternalUnifiedSerialisation { 7,
"hello world",
{ 5, 6, 7 },
{ { "foo", 4 }, { "bar", 5 } } });
expect (FromVar::convert<TypeWithInternalUnifiedSerialisation> (JSONUtils::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> (JSONUtils::makeObject ({ { "__version__", 10 },
{ "a", JSONUtils::makeObject ({ { "engaged", true }, { "value", "string" } }) },
{ "b", Array<var> { "0x1", "0x2", "0x3" } } }))
== TypeWithExternalSplitSerialisation { "string", { 1, 2, 3 } });
expect (FromVar::convert<TypeWithInternalSplitSerialisation> (JSONUtils::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> (JSONUtils::makeObject ({ { "a", 7.89 },
{ "b", 4.321f } }))
== std::nullopt);
expect (FromVar::convert<TypeWithVersionedSerialisation> (JSONUtils::makeObject ({ { "__version__", 3 },
{ "a", 1 },
{ "b", 2 },
{ "c", 3 },
{ "d", 4 } }))
== TypeWithVersionedSerialisation { 1, 2, 3, 4 });
expect (FromVar::convert<TypeWithVersionedSerialisation> (JSONUtils::makeObject ({ { "__version__", 4 },
{ "a", 1 },
{ "b", 2 },
{ "c", 3 },
{ "d", 4 } }))
== TypeWithVersionedSerialisation { 1, 2, 3, 4 });
expect (FromVar::convert<TypeWithVersionedSerialisation> (JSONUtils::makeObject ({ { "__version__", 2 },
{ "a", 1 },
{ "b", 2 },
{ "c", 3 } }))
== TypeWithVersionedSerialisation { 1, 2, 3, 0 });
expect (FromVar::convert<TypeWithVersionedSerialisation> (JSONUtils::makeObject ({ { "__version__", 1 },
{ "a", 1 },
{ "b", 2 } }))
== TypeWithVersionedSerialisation { 1, 2, 0, 0 });
expect (FromVar::convert<TypeWithVersionedSerialisation> (JSONUtils::makeObject ({ { "__version__", 0 },
{ "a", 1 } }))
== TypeWithVersionedSerialisation { 1, 0, 0, 0 });
expect (FromVar::convert<TypeWithVersionedSerialisation> (JSONUtils::makeObject ({ { "a", 1 } }))
== TypeWithVersionedSerialisation { 1, 0, 0, 0 });
const auto raw = JSONUtils::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[] { JSONUtils::makeObject ({ { "foo", 1 }, { "bar", 2 } }),
var (Array<var> { 1, 2 }),
var() };
for (const auto& payload : payloads)
{
const auto objectWithPayload = JSONUtils::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)
{
const auto text = a.has_value() && b.has_value()
? JSON::toString (*a) + " != " + JSON::toString (*b)
: String();
expect (deepEqual (a, b), text);
}
static bool deepEqual (const std::optional<var>& a, const std::optional<var>& b)
{
if (a.has_value() && b.has_value())
return JSONUtils::deepEqual (*a, *b);
return a == b;
}
};
static JSONSerialisationTest jsonSerialisationTest;
} // namespace juce
juce::FromVar::convert
static std::optional< T > convert(const var &v)
Definition juce_JSONSerialisation.h:267
juce::JSON::fromString
static var fromString(StringRef)
Definition juce_JSON.cpp:478
juce::JSON::toString
static String toString(const var &objectToFormat, bool allOnOneLine=false, int maximumDecimalPlaces=15)
Definition juce_JSON.cpp:513
juce::String::toHexString
static String toHexString(IntegerType number)
Definition juce_String.h:1097
juce::ToVar::convert
static std::optional< var > convert(const T &t, const Options &options={})
Definition juce_JSONSerialisation.h:87
juce::UnitTest
Definition juce_UnitTest.h:70
juce::UnitTest::UnitTest
UnitTest(const String &name, const String &category=String())
Definition juce_UnitTest.cpp:26
juce::UnitTest::beginTest
void beginTest(const String &testName)
Definition juce_UnitTest.cpp:89
juce::UnitTest::expect
void expect(bool testResult, const String &failureMessage=String())
Definition juce_UnitTest.cpp:97
juce::UnitTest::runTest
virtual void runTest()=0
juce::JSONUtils::deepEqual
static bool deepEqual(const var &a, const var &b)
Definition juce_JSONUtils.cpp:115
juce::JSONUtils::makeObject
static var makeObject(const std::map< Identifier, var > &source)
Definition juce_JSONUtils.cpp:26
juce::SerialisationTraits
Definition juce_Serialisation.h:66