Construct var programmatically and convert to JSON

Hi,

Is it possible to use var and JSON to build a json string? Like so:

var json; json["foo"] = "bar"; json["num"] = 123; json["nested"]["inner"] = "value"; String s = JSON::toString();

s contains “null”. What I expected was:

{ "foo" : "bar", "num" : 123, "nested" : { "inner": "value" } }

Patrick

shouldn’t it be JSON::toString(json) ?

well, for a start, that is true, but tbh I’m pretty sure that wouldn’t have compiled anyway [and so is likely just a typo in the post].

I think the real issue here is that var isn’t being used correctly.

A line like this…

… is basically meaningless. Using the [] operator on a var with an Identifier parameter is only useful if the var already/ holds a DynamicObject. It is effectively a shorthand for the following:

Even if the var DID hold a DynamicObject with the named property already defined, the line would still do nothing useful. The call on the left does not return a reference to the property, it returns a copy; thus, assigning this would carry over no result.

If you want to generate a complex structure like this, the class you will need to use directly is DynamicObject. If you were to do this backwards [take your expected JSON text and parse it into a var], the parser would actually generate DynamicObjects for each part of the data consisting of named properties. This (untested) code is more like what you would need to do for your example…

DynamicObject* obj = new DynamicObject();
obj->setProperty("foo","bar");
obj->setProperty("num",123);

DynamicObject* nestedObj = new DynamicObject();
nestedObj->setProperty("inner","value");
obj->setProperty("nested",nestedObj); 

var json (obj); // store the outer object in a var [we could have done this earlier]
String s = JSON::toString(json);

Of course, it’s not really surprising that it wasn’t obvious; the JSON class documentation could do with a bit of beefing up to explain some (any) of this :wink:

[NOTE: i’ve not compiled or tested the above code. Also, I’ve not actually tried using the var in this manner, but I’m optimistic that I’m not wrong!]

3 Likes

You are right, it was a typo. It should be JSON::toString(json); as atom said. Reminds me not to type code into the forum editor, better copy it directly from source.

Thanks for your explanation! It makes sense now that the String returned was null.

Awesome, I will try this.

Patrick

I should probably also clarify one thing I said above…

When I say that myVar[“name”] returns a ‘copy’, what I mean is it returns the value of the property in a var object. I don’t mean that it does any duplication of data any more complex than assignment of a basic type.

If you used your example of json[“nested”], the value returned would not be a ‘copy’ of the “inner” DynamicObject, it would in fact point to the same instance. This is not a special case or anything - the value held by the property is really a pointer to a DynamicObject, and that is what you receive.

I just wanted to make sure I wasn’t misleading anyone into thinking that large structures would be needlessly duplicated just by inspecting them in this way; the ‘copying’ will only ever be as complex as returning the value of a basic type.

Without the whole DynamicObject stuff:

var json, json2;
json.append ("element1");
json.append ("element2");
json2.append ("element3");
json2.append ("element4");
json.append (json2);
_DBG (JSON::toString (json));

OUTPUT IS:
[
  "element1",
  "element2",
  [
    "element3",
    "element4"
  ]
]

Yes, if you are just using lists, var (as an array) is fine by itself, but DynamicObjects are required for named fields.

I created this function to help solve this problem.  

void varSetProperty(var& object, String key, var value){

    if (object.isObject()

        object.getDynamicObject()->setProperty(key, value);

}

void someCode(){

var myJSONObject(new DynamicObject());

varSetProperty(myJSONObject, "name", "Jules");

}

Using object vars this way I can create any kind of JSON data programmatically.

Unless I am being dumb and missing something, it seems to me that the var class is missing a setProperty type of method like this.

I agree, I was looking for a method like the following:

 

void var::setProperty(String key, const var &value);

 

for building JSON objects. Jules, can or should this exist?

 

No, it can't have a method like that, because like Haydxn said earlier in this thread, you need to provide a DynamicObject to hold the properties, and to have it automatically create one for you the first time you try to set a property would be beyond the responsibilities of the var class. 

Woops, I'm sorry - should have Haydxn's post before complaining. Thank you!

Don't apologise - it was a fair question! And technically, it could be made to work that way, but I just think it's better for it not to create the DynamicObject automatically.

Just replying as a more ‘notes to myself’ about this:

I really like this function, especially when it’s treated as a lambda.
I think the hardest part about creating JSON programmatically is translating, if you know what you want the final tree to look like, the final tree into code.

Say I have a tree that I want to look like this:

{
    "id1": "value",
    "id2": "value",
    "id3": 
    {
        "id5": "value",
        "id6": "value"
    },
    "id4": 
    [
        {
            "id7": "value",
            "id8": "value"
        },
        {
            "id9": "value",
            "id0": "value"
        }
    ]
}

So, what does that look like programmatically? you gotta work your way from the inside out:

        {
            "id9": "value",
            "id0": "value"
        }

Using pneyrink’s function as a lambda:

auto JSONHelper =
    [](juce::var& object, juce::String key, juce::var value ){
        if( object.isObject() ) {
            object.getDynamicObject()->setProperty(key, value);
        }
    };

we can create the above JSON object simply:

var newObj90( new DynamicObject() );
JSONHelper( newObj90, "id9", "value" );
JSONHelper( newObj90, "id0", "value" );

So, let’s make that a lambda as well:

auto nestedObject = [JSONHelper](const String& id1, const String& val1, const String& id2, const String& val2, ) {
    var newObj( new DynamicObject() );
    JSONHelper( newObj, id1, val1 );
    JSONHelper( newObj, id2, val2 );
    return newObj;
}

if we have an array of these newObj’s, then that’s easy too:

var newArrayObj( new DynamicObject() );
JSONHelper( newArrayObj, "id4", Array<var>() );

and now just push those nested JSON objects into the array:

if( auto id4 = tree.getProperty("id4", var()).getArray() ) {
    id4->addIfNotAlreadyThere( nestedObject("id9", "value", "id0", "value") );
    id4->addIfNotAlreadyThere( nestedObject("id7", "value", "id8", "value") );
}

You can start to see that the tree’s structure looks like this, programmatically:

var tree( new DynamicObject() );
{
     JSONHelper( tree, "id1", "value" );
     JSONHelper( tree, "id2", "value" );
     JSONHelper( tree, "id3", nestedObject("id5", "value", "id6", "value" ) );
     JSONHelper( tree, "id4", Array<var>() );
     tree.getProperty("id4", var()).getArray()->addIfNotAlreadyThere( nestedObject("id9", "value", "id0", "value") );
     tree.getProperty("id4", var()).getArray()->addIfNotAlreadyThere( nestedObject("id7", "value", "id8", "value") );
}

you could take this a step further, modify nestedObject() such that it takes 2 arrays of vars, one for keys, and one for values. just make sure they’re the same size and loop thru them to populate the DynamicObject that’ll get placed in the node.

4 Likes