How to handle JSON parseCopy() errors?

I’m using the inbuilt JSON parser using code that is essentially identical to the first example in the documentation: Device OS API | Reference Documentation | Particle

It works fine unless the user sends an invalid JSON string in which case my device goes into blinking red state and reboots. Does anyone know what parseCopy() returns in case of errors and/or how best to handle these in a more robust way?

Do you have the exact JSON string that causes this? And does the SOS occur in parseCopy() or when accessing the data?

Thanks for the quick response.
Here's my code snippet:

Log.info("About to parse " + jsonString);
JSONValue outerObj = JSONValue::parseCopy(jsonString);
Log.info("Parse complete; About to declare JSONObjectIterator");
JSONObjectIterator iter(outerObj);
Log.info("JSONObjectIterator declared.  About to iterate using iter.next()");

And here are some results. First working ok:

0000089928 [app] INFO: About to parse {flashEnabled:true}
0000089929 [app] INFO: Parse complete; About to declare JSONObjectIterator
0000089929 [app] INFO: JSONObjectIterator declared. About to iterate using iter.next()
0000089930 [app] INFO: key=flashEnabled value=true

And here are some examples with bad json strings which go to SOS:

0000095784 [app] INFO: About to parse {flashEnabled}
0000095784 [app] INFO: Parse complete; About t

Here's another bad json string which goes to SOS

0000055071 [app] INFO: About to parse {flashEnabled;true}

The obvious answer is don't enter a bad json string(!), but in my case the source is ultimately the user who can make mistakes, so I would like my code to be able to catch these and respond appropriately. Any thoughts on how to do this would be much appreciated...

Hi, how is the user entering the JSON string? a mobile app or website?
Can you validate the string right there and not on the Particle device?

I believe you should structure it like this:

JSONValue outerObj = JSONValue::parseCopy(jsonString);
if (outerObj.isValid()) {
    Log.info("Parse complete; About to declare JSONObjectIterator");
    JSONObjectIterator iter(outerObj);
    Log.info("JSONObjectIterator declared.  About to iterate using iter.next()");
}
else {
    Log.info("invalid JSON");
}

The parse() and parseCopy() methods always return an object, but it may return an empty object. It looks like your parse call is returning successfully, but the next operation is what is failing.

Thanks @rickkas7. I think this is a step forward, but the isValid() test does not appear to fail when it should, so it still goes to SOS.

My code is very similar to your suggestion:

	Log.info("About to parse " + jsonString);
    JSONValue outerObj = JSONValue::parseCopy(jsonString);
	if (!outerObj.isValid()) {
		Log.info("invalid JSON");
		return(-1);
	} else {
    	Log.info("Parse complete; About to declare JSONObjectIterator");
    	JSONObjectIterator iter(outerObj);
    	Log.info("JSONObjectIterator declared.  About to iterate using iter.next()");
		while(iter.next()) {

The result varied slightly if I repeated the test but always ended in SOS.
The furthest it got was:

0000020028 [app] INFO: About to parse {flashEnabled;true}
0000020028 [app] INFO: Parse complete; About to declare JSONObjectIterator
0000020029 [app] INFO: JSONObjectIterator declared.  About to iterate using iter.next()

but on other occasions it only got as far as the first or second INFO message. Is there an iter.isValid() function I could call before attempting to access iter.name() and iter.value()?

You could try outerObj.isValid() && outerObj.isObject() to make sure it's detected as an object before trying to iterate it.

Sorry for my slow reply + I will be travelling for the next couple of weeks so may need to pick this up again on my return, but I'm so grateful any help / comments in the meantime...

This is still very puzzling. I added @rickkas7 suggested checks + added some delays to ensure my debug messages were printed before we crashed, so the code now looks like this:

	Log.info("About to parse " + jsonString);
    JSONValue outerObj = JSONValue::parseCopy(jsonString);
	if (!outerObj.isValid()|| !outerObj.isObject()) {
		Log.info("invalid JSON string");
		return(-1);
	} else {
    	Log.info("Parse complete; About to declare JSONObjectIterator");
    	JSONObjectIterator iter(outerObj);
		Log.info("JSONObjectIterator declared.  About to iterate using iter.next()");
		delay(200);
		Log.info("Count= " + String(iter.count()));
		delay(200);
		bool moreToCome = iter.next();
		Log.info("iter.next() = " + String(moreToCome));
		delay(200);
		while (moreToCome) {
			Log.info("Key= " + String(iter.name()));
			delay(200);
			if (iter.value().isNull()) {
				Log.info("Value= NULL");
				delay(200);
			} else {       
				Log.info("Value= " + String(iter.value().toString()));
				delay(200);
			}
...etc

As seen from the results (below), it fails (goes to SOS) when we try to access the non-existent iter.value() field. I tried to test for this in my code using iter.value().isNull() but perhaps one cannot call isNull() on a non-existent value field!

0000039604 [app] INFO: About to parse {flashEnabled;true}
0000039605 [app] INFO: Parse complete; About to declare JSONObjectIterator
0000039605 [app] INFO: JSONObjectIterator declared. About to iterate using iter.next()
0000039808 [app] INFO: Count= 1
0000040008 [app] INFO: iter.next() = 1
0000040209 [app] INFO: Key= flashEnabled;true
... and it then goes to SOS

So I'm wondering:

  • Is there a robust way to detect an empty value field?
  • Can anyone replicate this problem, or is it just me?

A key without a value is not valid in JSON, and that's what you have there.

And I believe it's a bug in the JSON wrapper that it assumes that if you have an object (you do), and the iterator is currently valid (also true for you), that there will be two key or value items (not true in your case). The JSONObjectIterator::iter() accesses the value without checking to see if the second exists.

I would add a check for iter.count() is >= 2 before accessing the key and value for the pair if you must be able to handle a case of invalid JSON containing a key without a value as the last element of the object.

Many thanks! What do you think the chances are for getting this bug fixed? I suspect I'm an outlier, so it might not be the highest priority!

In the meantime, I think you're suggesting that if iter.count() is odd, then it could signify a missing value field. I'm probably misunderstanding, but iter.count() seems to return the number of key:value pairs not the number of fields. Thoughts?

Yes, you are right, count() is the number of pairs, so that won't help. I can't see a way to make it work.

I tested it using JsonParserGeneratorRK and it returns false when searching for a value by key name from invalid JSON instead of causing a SOS.

	{
		JsonParserStatic<256, 14> jp;
		String s;

		jp.addString("{flashEnabled;true}");

		bool bResult = jp.parse();
		assert(bResult);

		bool bValue;

		bResult = jp.getValueByKey(jp.getOuterObject(), "flashEnabled", bValue);
		assert(!bResult); // Not valid

	}