ValueTree array to xml


#1

Hello,

I’m looking into the ValueTree class, and am having trouble with the storing of an array. I can’t seem to get an array to store in an xml file. Everything seems to throw an assertion in NamedValueSet::copyToXmlAttributes as objects and arrays can’t be saved as xml. The docs seem to suggest that a var can store an array, and parse down to xml. Is this not the case?

ValueTree tree("Tree");
var intArray({12,12,23,23});
Array<var> varArray = {12,12,23,23};
tree.setProperty("IntArray", intArray, nullptr);
tree.setProperty("VarArray", varArray, nullptr);
DBG(tree.toXmlString());

#2

I don’t think this is possible, but I could be wrong. I don’t see anything in the docs, but I think I read in the code base that the toXmlString() method only deals with simple data types? Anyone else know for sure?


#3

Such a shame as the value tree is pretty useful. Is there anyway to override the value tree parse method to serialise my own object?


#4

XML and JSON have different structures, and XML properties are just strings, they can’t contain arrays. If you’re trying to generate XML then you need to think about it in terms of what you want your XML to look like, and build a ValueTree or XmlElement appropriately.

Or, if you just want to serialise a var, use JSON instead, since that’s what a var actually represents.


#5

If you rely on XML you can also parse the array as JSON string and save this string as XML attribute. Its rather ugly but works.


#6

I use code like this to put arbitrary objects in arrays into a ValueTree.

template <typename T>
void serializeArray(ValueTree & target, const T & source)
{
	for (auto & l : source)
	{
		auto t = l->getSerialized();
		target.addChild(t, -1, nullptr);
	}
}

Maybe something like this adaption works for plain data types if you just want to copy data into a ValueTree in a useful way:

template <typename T>
void serializeArray(ValueTree & target, const T & source)
{
	for (auto & l : source)
	{
		ValueTree e{"element"};
		e.setProperty("value", l, nullptr);
		target.addChild(e, -1, nullptr);
	}
}

Or sometimes I use this, although I’m not sure there wasn’t a bug in it ;_) But conceptually this is an array of ItemType stored in an array where the ItemType has a constructor that takes a ValueTree into which it stores all it’s data. This has the advantage that the data is backended onto the ValueTree properly so the ValueTree is always up to date and all the listener stuff works, and things can share the same data.

It probably needs an example…

template <class ItemType>
class ValueTreeArray
{
public:

	class Iterator
	{
	public:
		Iterator(const ValueTreeArray* ptr, int pos)
			: pos(pos), ptr(ptr)
		{ }
		bool operator!= (const Iterator& other) const { return pos != other.pos; }
		ItemType operator* () const { return ptr->get(pos); };
		const Iterator& operator++ () { ++pos; return *this; }
	private:
		int pos;
		const ValueTreeArray* ptr;
	};


	ValueTreeArray(const String & nodeTypeName, const ValueTree & treeForData)
		:
		nodeTypeName(nodeTypeName),
		tree(treeForData)
	{
		valueTreeAction.setValueTree(tree); 
		valueTreeAction.setAction(
			[this]()
		{
			listeners.call(&Listener::arrayDataUpdated); 
		});

		jassert(tree != ValueTree::invalid);
	}

	~ValueTreeArray() {}

	ItemType operator[] (int index)
	{
		return ItemType(tree.getChild(index));
	}
	ItemType get(int index) const
	{
		return ItemType(tree.getChild(index));
	}

	Iterator begin() const { return Iterator(this, 0); }
	Iterator end() const { return Iterator(this, size()); }
	int size() const { return tree.getNumChildren(); }

	ItemType getNewNode()
	{
		ValueTree itemTree(nodeTypeName);
		tree.addChild(itemTree, -1, nullptr);
		ItemType newItem(itemTree);
		return newItem;
	}

	/** Inserts something after the given location. */
	ItemType insertAfter(int location)
	{
		ValueTree itemTree(nodeTypeName); 
		tree.addChild(itemTree, location + 1, nullptr);
		ItemType newItem(itemTree);
		return newItem;
	}


	/** Calls getTree() on the ItemType and adds it to the
	array. */
	void add(ItemType item)
	{
		tree.addChild(item.getTree(), -1, nullptr);
	}

	void remove(int index) { tree.removeChild(index, nullptr); }
	void clear() { tree.removeAllChildren(nullptr); } 

	bool operator== (const ValueTreeArray& rhs) const
	{
		return tree.isEquivalentTo(rhs.tree);
	}

	class Listener
	{
	public:
		virtual ~Listener() {}
		virtual void arrayDataUpdated() = 0; 
	};

	void addListener(Listener * listener)
	{
		listeners.add(listener);
	}

private:
	ListenerList<Listener> listeners;
	ValueTreeActionOnAnyChange valueTreeAction;
	String nodeTypeName;
	ValueTree tree;
	JUCE_LEAK_DETECTOR(ValueTreeArray)
};

Storing Array<Point<float>> in ValueTree