I’ve got a project on the go at the moment where the data is stored in native types. On save and load the data is transferred to or from a ValueTree, which is stored on disk as XML. It works well, but each class has a serialize and deserialize method, the contents of which need to match. There’s too much duplication and scope for error.
Anyway - someone showed me the boost serialize class. And I needed an excuse to play with more complex template stuff as well … not my usual area.
So here’s what some demo classes look like:
class Award
{
public:
void saveload(ValueTreeArchiver::Archiver & ar)
{
ar.copy(name, "name");
}
String name;
};
class Person
{
public:
void saveload(ValueTreeArchiver::Archiver & ar)
{
ar.copy(name, "name");
ar.copy(age, "age");
ar.copy(height, "height");
ar.copy(awards, "awards");
}
String name;
int age{ 0 };
double height{ 0.0 };
std::vector<Award> awards;
};
And here’s a basic test which passes:
Person p;
p.name = "Fred";
p.age = 69;
p.height = 6.0;
p.awards.push_back({ "Greatest Man Alive" });
p.awards.push_back({ "Ate the most Onions" });
/* Saving ... */
ValueTree savedData{ "person" };
ValueTreeArchiver::Archiver archiver;
archiver.save(p, savedData); // < this is all there is to saving
DBG(savedData.toXmlString());
/* Loading ... */
Person q;
archiver.restore(q, savedData);
/* Now q is identical to p. Let's test that:- */
jassert(q.awards[0].name == "Greatest Man Alive");
jassert(q.age == 69);
I think that’s pretty successful and a lot simpler than having a separate save and load method. Also the handling of the vector is 100 times easier.
And here’s the horrible template shit:
namespace ValueTreeArchiver
{
enum MethodType
{
direct,
container
};
template<typename T> struct DirectlySupported { static const MethodType value = container; };
template<> struct DirectlySupported<float> { static const MethodType value = direct; };
template<> struct DirectlySupported<String> { static const MethodType value = direct; };
template<> struct DirectlySupported<double> { static const MethodType value = direct; };
template<> struct DirectlySupported<int> { static const MethodType value = direct; };
template<> struct DirectlySupported<bool> { static const MethodType value = direct; };
/** Default method uses valuetree directly */
template <MethodType t>
struct Method
{
template <typename T>
static void save(T & value, const Identifier & identifier, ValueTree & tree)
{
tree.setProperty(identifier, value, nullptr);
}
template <typename T>
static void load(T & value, const Identifier & identifier, ValueTree & tree)
{
value = tree[identifier];
}
};
/** If we aren't a simple class assume we are something handled by a container .. */
template <>
struct Method<container>
{
template <typename T>
static void save(T& value, const Identifier& identifier, ValueTree& tree);
template <typename T>
static void load(T& value, const Identifier& identifier, ValueTree& tree);
};
class Archiver
{
public:
template <typename T>
void copy(T & value, const Identifier & identifier)
{
if (isSaving)
Method<DirectlySupported<T>::value>::save(value, identifier, t);
else
Method<DirectlySupported<T>::value>::load(value, identifier, t);
}
template <class C>
void restore(C & baseObject, const ValueTree & savedData)
{
t = savedData;
isSaving = false;
baseObject.saveload(*this);
}
template <class C>
void save(C & baseObject, const ValueTree & treeToWriteTo)
{
t = treeToWriteTo;
isSaving = true;
baseObject.saveload(*this);
}
bool isSaving{ false };
ValueTree t;
};
template <typename T>
void Method<container>::save(T& value, const Identifier& identifier, ValueTree& tree)
{
ValueTree container{ identifier };
tree.addChild(container, -1, nullptr);
for (auto & v : value)
{
Archiver archiver;
ValueTree n{ "node" };
archiver.save(v, n);
container.addChild(n, -1, nullptr);
}
}
template <typename T>
void Method<container>::load(T& value, const Identifier& identifier, ValueTree& tree)
{
auto parent = tree.getChildWithName(identifier);
auto numChildren = parent.getNumChildren();
for (int i = 0; i < numChildren; ++i)
{
auto n = parent.getChild(i);
Archiver archiver;
typename T::value_type newObject;
archiver.restore(newObject, n);
value.push_back(std::move(newObject));
}
}
}
Any thoughts and feedback much appreciated. I think I’d like to special case things like OwnedArray, and I’m not really happy with how it defaults to assuming a C++11 container. It works here, but there’s probably some fancy way of detecting containers using type traits I need to look up.


