Problems with JSON

Long time since using C++. Initially trying to get a UDP connection to a server that takes JSON strings.
In this code I can’t seem to get the array working. The output shows a bool where the array should be.

{
“cmd”: “radio_discover”,
“params”: false
}, 51

var v(new DynamicObject());
var a(new Array<int>);

v.getDynamicObject()->setProperty("cmd", m.msg_name);
v.getDynamicObject()->setProperty("params", a.getArray());
String s = JSON::toString(v);
const char* new_cstring = static_cast<const char*> (s.toUTF8());
printf("%s, %d\n", new_cstring, strlen(new_cstring));
sock.write(SERVER_IP, SERVER_CMD_PORT, (void *)new_cstring, strlen(new_cstring));

Seems the compiler is way too forgiving:

a) the only constructor to feed an array into a var takes an Array<var>. There is no relation between Array<int> and Array<var>, templates don’t inherit. So the var a was empty from the start.

b) a.getArray() will return a nullptr, which is implicitly casted to false

The simplest way to form an array in a var is to call a.append (5);
Appends an element to the var, converting it to an array if it isn't already one.

One way would be:

auto* obj = new DynamicObject();
obj->setProperty("cmd", m.msg_name);
var a (1);
a.append (2);                  // converts a into an array according to the docs
a.append (3);
obj->setProperty("params", a); // no need to convert to an array, the var will do
var v (obj);                   // takes ownership of obj
auto asString = JSON::toString (v);
sock.write(SERVER_IP, SERVER_CMD_PORT, asString.toRawUTF8(), asString.getNumBytesAsUTF8());

Hope that helps

Thanks. That does work and is obviously nicer code but to my dynamic languages head it seems odd that I have to create something then append something to make an array. Is there no way to have an empty array which some of the calls expect when there are no parameters.

Sure, there are nicer ways to write that, e.g.

var v (Array<var>());

Also worth noting that the example above is not exception-safe because if something was to throw after creating the DynamicObject and before wrapping it in a var, then it would leak. It’s good practice to always capture the DynamicObject as soon as it’s created, so a cleaned up version would be:

DynamicObject::Ptr obj = new DynamicObject();
obj->setProperty ("cmd", m.msg_name);
obj->setProperty ("params", Array<var> { 1, 2, 3 });

auto asString = JSON::toString (obj.get());

sock.write(SERVER_IP, SERVER_CMD_PORT, asString.toRawUTF8(), asString.getNumBytesAsUTF8());

Thinking about your original confusion, your mistake was nothing to do with it being an Array<int>, it was the fact that you gave it a raw pointer, and when C++ was deciding which constructor you wanted, the only one it found that matched was to convert the pointer to a bool, which was obviously not what you had in mind!

So we should probably add a deleted var (void*) constructor which I think would have thrown up an error instead of silently doing the wrong thing in your case.

I’m also now wondering whether we should deprecate the creation of a var from a DynamicObject raw pointer and force people to give it a smart-pointer instead to stop people doing what Daniel did above (which I suspect is very common), though that would probably force a lot of people to change existing code.

2 Likes

By the way, while the JUCE JSON implementation works great and does all you need, I prefer to use the JSON for Modern C++ header only library which allows you to use a very convenient and readable syntax for handling JSON data for projects that make heavy use of JSON. Your code would then look like this

nlohmann::json v;

v["cmd"]    = m.msg_name.toStdString(); // assuming msg_name is a juce::String
v["params"] = { 1, 2, 3 };

auto asPrettyStdString = v.dump (2); // indents the JSON structure by two whitespaces
std::cout << asPrettyStdString << std::endl;

sock.write(SERVER_IP, SERVER_CMD_PORT, asPrettyStdString.c_str(), asPrettyStdString.length());

The biggest downside of this approach beneath the extra dependency is that this class obviously is designed to use the std library types, e.g. std::string or std::vector so you might have to do some more coversions between juce and std types.

1 Like

Thanks all for the info and explanations. I’ve never used C++ in anger so I’m going to make a lot of mistakes converting my Python/Qt app into Juce but I need the performance and the Juce ecosystem looks very nice and has all the bits I need so I will take my time and make sure I understand my mistakes as I go.

Only if they update their version of JUCE to the latest development tip! I truly feel like that is no reason not to push through with this improvement. anyone using the latest develop branch commit knows that the framework is changing daily and will adapt to these kinds of improvements without question.

Oh, I wasn’t aware of the DynamicObject::Ptr, very nice.

1 Like

As this is the same bit of code I will raise it here but if I should open a new topic please say. So, I can send multiple requests to my server.

cmd_exchange(M_ID::DISCOVER, Array {});
cmd_exchange(M_ID::SVR_START, Array {});
cmd_exchange(M_ID::RADIO_START, Array {0});

They just end up sending a different message. The last send repeats exactly 101 times. I definitely only call write once per message but the server gets multiple messages. I know this can happen with UDP but not 101 times and not on a localhost connection. Its only the last message or the only message that repeats. Very strange. The server has been used from a number of languages but never seen this behavior before.

Forget that, it’s my server returning -1 for the read sz which wasn’t checked and the read buffer wasn’t cleared. Strange that I’ve never seen it do this before.

If you just want to create and manipulate JSON objects and convert to string, I would also recommend the nlohmann json library @PluginPenguin mentioned. Super easy to add to your project and very nice to use.