Convert ValueTree to DynamicObject?

Is there an easy way to convert ValueTree to DynamicObject and back?

I’m creating an app with javascript and I need to manipulate / pass around value tree stuff between C++ and Javascript. I want to manipulate a value tree with javascript.

I guess the main issue is I want to store a value tree child inside a var.

Edit: The one way I can think of is storing ValueTree as an xml String var for javascript, and then converting xml back to value tree for c++. But that would suck because then I couldn’t do things like:

item = itemlist.getItem(0); myName = item.name;

It would have to be:

item = itemlist.getItem(0); myName = xmlThing.getProperty("NAME", item);

Edit: Hmm, another problem is setting the name. If I set the name the valuetree needs to be updated:

itemlist.getItem(0).setName("newname");

If I can’t update it with that kind of method call, then I need to do:

itemlist.setItemName(0, "newname");

unless of course I create many methods, many object types, and many properties for every possibility to communicate between C++ ValueTree and javascript objects.

1 Like

I am using a similar concept for converting the ValueTree of my UI objects into a JSON representation:

<Component type="ScriptPanel" id="Panel1" x="156" y="94" width="373" height="302">
  <Component type="ScriptSlider" id="Knob1" x="84.00000000" y="116.00000000"
             parentComponent="Panel1"/>
  <Component type="ScriptSlider" id="Knob2" x="128.00000000" y="33" parentComponent="Panel1"
             width="198.00000000"/>
</Component>

becomes this:

{
  "type": "ScriptPanel",
  "id": "Panel1",
  "x": 156,
  "y": 94,
  "width": 373,
  "height": 302,
  "childComponents": [
    {
      "type": "ScriptSlider",
      "id": "Knob1",
      "x": 84.0,
      "y": 116.0,
      "parentComponent": "Panel1"
    },
    {
      "type": "ScriptSlider",
      "id": "Knob2",
      "x": 128.0,
      "y": 33,
      "parentComponent": "Panel1",
      "width": 198.0
    }
  ]
}

You might notice that I don’t care about the tag name of the XML at all - they’re all named Component. Also you need to introduce a artificial property that holds the child elements in a list.

Now as far as the implementation goes, you can’t operate on the actual ValueTree object directly - unless you subclass DynamicObject and it’s getter / setter methods, but I am not sure if this works. So you definitely need some kind of flush function:

var myObject = ValueTreeClass.get("myFunkyTree");
myObject.name = "newName";
ValueTreeClass.flush("myFunkyTree", myObject);

First line converts ValueTree to DynamicObject, second line does some operation and the third line writes the DynamicObject back to the value tree.

1 Like

I’m trying to create valueTreeToVar recursive function, but I’m stuck. Not sure how to add children. Only got the properties so far.

var valueTreeToVar(ValueTree& node, var& result)
{
	for (auto i = 0; i < node.getNumProperties(); ++i)
	{
		auto propertyName = node.getPropertyName(i);
		auto propertyValue = node.getPropertyPointer(propertyName);

		result.getDynamicObject()->setProperty(propertyName, propertyValue);
	}

	for (auto i = 0; i < node.getNumChildren(); ++i)
	{
		auto id = node.getChild(i).getType();

		result.getDynamicObject()->setProperty(id, {});

		valueTreeToVar(node.getChild(i), result);
	}

	juce::MemoryOutputStream stream;

	result.getDynamicObject()->writeAsJSON(stream, 0, false);
	DBG(stream.toString());

	return result;
}

You’re almost there. Just pass in the child object into the recursive function call to build up the tree:

void valueTreeToVar(const ValueTree& node, var& result)
{
    static const Identifier id_("id");
    result.setProperty(id_, node.getType().toString());

	for (auto i = 0; i < node.getNumProperties(); ++i)
	{
		auto propertyName = node.getPropertyName(i);
		auto propertyValue = node.getPropertyPointer(propertyName);

		result.getDynamicObject()->setProperty(propertyName, propertyValue);
	}

    // this will hold our elements
    Array<var> childList;

     // you can use the iterator loop for child value trees.
	for (const auto& child: node) 
	{
        var childObject(new DynamicObject());

		// You don't need to do this here, the id will be set 
        // in the recursive function call 
        //result.getDynamicObject()->setProperty(id, {});

        valueTreeToVar(child, childObject);
        childList.add(childObject);
	}

    static const Identifier children_("children");
    result.setProperty(children_, childList);

}

The debugging code has to be moved outside of the recursive function or it will get executed for every child element.

DISCLAIMER: I typed the code in here, there might be some silly typos.

The debugging code has to be moved outside of the recursive function or it will get executed for every child element.

that’s the point!

Thanks for the help.

So now I’m having trouble using the var. I get a crash because I think the dynamicObject is getting deleted.

static var valueTreeToVar(const var::NativeFunctionArgs& args)
{
	RppReader rppReader(args.arguments[0].toString());

	DynamicObject dynamicObject;

	var returnVar(&dynamicObject);

	rppReader.valueTreeToVar(rppReader.getValueTree(), returnVar);

	return returnVar;
}

It’s absolutely getting deleted. It would behoove you to understand ‘scope’ as it relates to the lifetimes of objects. But, to get you going, you want:

var returnVar (new DynamicObject)

1 Like

How do you convert var back to ValueTree?

p.s. this is working code for valueTreeToVar, the original code before this post has a few mistakes I think.

void RppProject::valueTreeToVar(ValueTree node, var& result)
{
	result.getDynamicObject()->setProperty("id", node.getType().toString());

	for (auto i = 0; i < node.getNumProperties(); ++i)
	{
		auto propertyName = node.getPropertyName(i);
		auto propertyValue = node.getPropertyPointer(propertyName)->toString();

		result.getDynamicObject()->setProperty(propertyName, propertyValue);
	}

	Array<var> childList;

	for (int i = 0; i < node.getNumChildren(); ++i)
	{
		const auto& child = node.getChild(i);

		var childObject(new DynamicObject());
		valueTreeToVar(child, childObject);

		childList.add(childObject);

		result.getDynamicObject()->setProperty("children", childList);
	}
}

Here is some code that might be a starting point. It’s still an early on in it’s implementation, in that I just codified it a few weeks ago, to help me process JSON input. But I think the basic ‘tree walking’ structure of it will help you build some code to make ValueTree’s.

VarHelpers.h

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

namespace VarHelpers
{
    void addProperty (const var& object, String key, var value);
    var addSection (const var& object, String key);
    void forEachSection (const var& object, std::function<void (String key, const var& section)> sectionCallback);
    void forEachProperty (const var& object, std::function<void (String key, const var& property)> propertyCallback);
    String getVarAttribString (const var& theVar);
    void dumpVar (const var& theVar);
    const var& getSection (const var& theVar, Identifier key);
        
    template<class T>
    bool hasProperty (const var& theVar, Identifier property)
    {
        if (theVar.hasProperty (property))
        {
            if constexpr (std::is_same_v<T, int>)
            {
                if (theVar[property].isInt ())
                    return true;
            }
            else if constexpr (std::is_same_v<T, int64>)
            {
                if (theVar[property].isInt64 ())
                    return true;
            }
            else if constexpr (std::is_same_v<T, bool>)
            {
                if (theVar[property].isBool ())
                    return true;
            }
            else if constexpr (std::is_same_v<T, double>)
            {
                if (theVar[property].isDouble ())
                    return true;
            }
            else if constexpr (std::is_same_v<T, String>)
            {
                if (theVar[property].isString ())
                    return true;
            }
            else if constexpr (std::is_same_v<T, Array<var>>)
            {
                if (theVar[property].isArray ())
                    return true;
            }
        }
        return false;
    }

    template<class T>
    T getProperty (const var& theVar, Identifier property)
    {
        if constexpr (std::is_same_v<T, String>)
            return theVar[property].toString ();
        else
            return (T)theVar[property];
    }
};

VarHelpers.cpp

#include "VarHelpers.h"

namespace VarHelpers
{
    void addProperty (const var& object, String key, var value)
    {
        if (!key.isEmpty () && object.isObject ())
            object.getDynamicObject ()->setProperty (key, value);
    };

    var addSection (const var& object, String key)
    {
        if (!key.isEmpty () && object.isObject ())
        {
            var newSection (new DynamicObject);
            addProperty (object, key, newSection);
            return newSection;
        }

        return {};
    }

    void forEachSection (const var& object, std::function<void (String key, const var& section)> sectionCallback)
    {
        if (object.isObject ())
        {
            const auto dynamicObject = object.getDynamicObject ();
            const auto namedPropertySet = dynamicObject->getProperties ();
            for (const auto& namedProperty : namedPropertySet)
            {
                if (namedProperty.value.isObject ())
                    sectionCallback (namedProperty.name.toString (), namedProperty.value);
            }
        }
    }

    void forEachProperty (const var& object, std::function<void (String key, const var& property)> propertyCallback)
    {
        if (object.isObject ())
        {
            const auto dynamicObject = object.getDynamicObject ();
            const auto namedPropertySet = dynamicObject->getProperties ();
            for (const auto& namedProperty : namedPropertySet)
            {
                if (!namedProperty.value.isObject ())
                    propertyCallback (namedProperty.name.toString (), namedProperty.value);
            }

        }
    }

    String getVarAttribString (const var& theVar)
    {
        if (theVar.isVoid ())
            return "void";
        else if (theVar.isInt ())
            return "int";
        else if (theVar.isInt64 ())
            return "int64";
        else if (theVar.isBool ())
            return "bool";
        else if (theVar.isDouble ())
            return "double";
        else if (theVar.isString ())
            return "String";
        else if (theVar.isObject ())
            return "Object";
        else if (theVar.isArray ())
            return "Array";
        else if (theVar.isBinaryData ())
            return "BinaryData";
        else if (theVar.isMethod ())
            return "Method";

        return "unknown";
    };

    void dumpVar (const var& theVar)
    {
        if (theVar.isObject())
        {
            std::function<void (const var& theVar, String name, int indentLevel)> dumpSections = [&dumpSections](const var& theVar, String name, int indentLevel)
            {
                Logger::outputDebugString (String (std::string (indentLevel, ' ')) + String ("<") + (name.isEmpty () ? String ("noname") : name) + String (">"));
                VarHelpers::forEachProperty (theVar, [indentLevel](String key, const var& value)
                    {
                        Logger::outputDebugString (String (std::string (indentLevel + 1, ' ')) + key + " : " + VarHelpers::getVarAttribString (value));
                    });
                VarHelpers::forEachSection (theVar, [&dumpSections, indentLevel](String key, const var& value)
                    {
                        dumpSections (value, key, indentLevel + 1);
                    });
            };
            dumpSections (theVar, "", 0);
        }
        else
        {
            Logger::outputDebugString ("var type : " + VarHelpers::getVarAttribString (theVar));
        }
    }

    const var emptyVar;

    const var& getSection (const var& theVar, Identifier key)
    {
        if (const auto& section = theVar[key]; section.isObject ())
            return section;
        else
            return emptyVar;
    }
};
1 Like