FR: JSON quality of life improvements

I’m currently trying to remove as many dependancies as possible from my app. Currently thinking it would be nice to use JUCE’s json parsing rather than a 3rd party library like nlohmann. However, juce::var lacks some functions that would make handling the parsed json a lot less verbose.

juce::var has getProperty and hasProperty, but no getProperties.

Iterating over properties is a pain. You need to get the dynamic object from the var, from the dynamic object you need to get the NamedValueSet, and then you can iterate that. Adding begin/end to var, would make things a lot easier, especially if they could do both arrays and objects.

Finally, JSONUtils supports using json pointers for setting values, but not getting values.

I have created potential implementations here: Figbug/variantimprovements by FigBug · Pull Request #1474 · juce-framework/JUCE · GitHub

Example:

juce::String json = 
R"(
{
	"id" : "root",
	"children" : 
	[
		{ "id": "child1", "name": "bob", "size": 2 },
		{ "id": "child2", "name": "sam", "size": 4 },
		{ "id": "child3", "name": "joe", "size": 8 },
		{ "id": "child4", "name": "rex", "size": 9 }
	]
}
)";

auto obj = juce::JSON::parse (json);

DBG(obj.getProperties().joinIntoString (", "));
DBG("====");

DBG(juce::JSONUtils::getPointer (obj, "/children/2/name", {}).toString());
DBG("====");

for (auto itr : juce::JSONUtils::getPointer (obj, "/children/2", {}))
	DBG(itr.name.toString() + ": " + itr.value.toString());
DBG("====");

for (auto itr : juce::JSONUtils::getPointer (obj, "/children", {}))
	DBG(itr.name.toString() + ": " + juce::JSON::toString (itr.value));
DBG("====");

for (auto itr : juce::var (3))
	DBG(itr.name.toString() + ": " + itr.value.toString());

Output:

id, children
====
joe
====
id: child3
name: joe
size: 8
====
0: {
  "id": "child1",
  "name": "bob",
  "size": 2
}
1: {
  "id": "child2",
  "name": "sam",
  "size": 4
}
2: {
  "id": "child3",
  "name": "joe",
  "size": 8
}
3: {
  "id": "child4",
  "name": "rex",
  "size": 9
}
====

Example of how the above with the current version of juce. Note it’s much more verbose, and I haven’t even put in the required nullptr checks, which would make it ever more verbose.

	DBG ([&obj]
	{
		juce::StringArray res;
		if (auto dyObj = obj.getDynamicObject())
			for (auto itr : dyObj->getProperties())
				res.add (itr.name.toString());
		return res;
	}().joinIntoString(","));
	DBG("----");

	DBG (obj.getProperty ("children", {}).getArray()->getUnchecked (2).getProperty("name", {}).toString());
	DBG("----");

	for (auto itr : obj.getProperty ( "children", {}).getArray()->getUnchecked (2).getDynamicObject()->getProperties())
		DBG(itr.name.toString() + ": " + juce::JSON::toString (itr.value));
	DBG("----");

	for (auto idx = 0; auto itr : *obj.getProperty ( "children", {}).getArray())
		DBG(juce::String (idx++) + ": " + juce::JSON::toString (itr));
	DBG("----");
5 Likes

Nice work, I’d use this almost immediately if the PR were merged. I’ll wait, though.

The other thing that is weird is that JSONUtils::setPointer clones the entire object hierarchy just to update one value. I can only find one place it’s used in the JUCE codebase, and it then assigns the cloned var back to the original var. Why not just update the existing var? Probably can’t change the API now, so I’ve added JSONUtils::updatePointer which behaves more like I’d expect.

We’ve merged a couple of improvements for working with var and JSON:

1 Like