Trouble with JSON parsing / variable conversion

I need a little help getting over a programming “hump”. I’m a little lost in a few aspects of C++ but here’s what I’m trying to do. I want my photon devices to communicate by passing JSON messages via particle cloud or UDP. I’m trying to be as efficient as possible with memory space right from the start because JSON messages could be quite large. To that end, I’m trying to use pointers wherever possible to minimize copying large strings multiple times. In my project, I’m limiting the max JSON buffer to 512 bytes at the moment. I keep getting stuck on passing the data from the particle cloud event into the JSON parser.

On the event handler function, I keep getting an error about “invalid conversion from ‘char’ to ‘const char*’ [-fpermissive]” in the CmdSubscribeHandler function on the line for strcpy(jsonString, &data). I get it, i’m not handling the character arrays properly, I just don’t know how to fix. I’ve tinkered with the pointers so much that I’m getting lost in syntax problems. At one point I did have the program working to the point where the event data was being passed to the command processor but the serial output for the raw data would only show 1 character “m”. I figured I had a problem where I was outputting the pointer instead of the real data. I tried making a local variable jsonString so that the message I’m parsing doesn’t get overwritten if a new cloud event comes in before parsing the first message was complete. (I’m not sure if that’s a real concern.)

Would someone please guide me to the best solution (minimal memory duplication) for handling the cloud event data and getting it it to the parser successfully? Is the srtcpy function necessary? Can I just use the pointer for *data without fear of it being overwritten by incoming events?

In the global setup area of the main program:

CmdProcessor cp(&s,&mo);

This is my particle cloud event handler:

void CmdSubscribeHandler(const char *event, const char *data) 
{
    char jsonString[512];
    strcpy(jsonString, *data);
    if (s.Debug) { Serial.printlnf("DSBcmd message received: %m", jsonString); }
    //if (s.Debug) { Serial.println(jsonString); }
    cp.ParseNew(&jsonString);
}

This is my CmdProcessor::ParseNew() function:
#include <SparkJson.h>  //Using the SparkJson library in the CmdProcessor Class.

void CmdProcessor::ParseNew(char (*NewCmd)[512]) 
{
    StaticJsonBuffer<512> jsonBuffer;
    
    //Testing string.
    //char json[] =
    //    "{\"id\":\"AAA\",\"setting\":1351824120,\"data\":[48.756080,2.302038]}";
    
    JsonObject& root = jsonBuffer.parseObject(*NewCmd);
    
    if (!root.success()) {
        Serial.println("JSON parseObject() failed. Attempted to parse: ");
        Serial.println(*NewCmd);
        return;
    }
    
    for (auto id : root) {
        char myKey[3];
        strcpy(myKey, id.key);
        
        if (s_cp->Debug) { Serial.printlnf("New cmd targets: %c", myKey); }
        
        if (AppliesToMe(&myKey)) {
            if (s_cp->Debug) { Serial.println("Cmd applies to me."); }
            
            //Parsing code here...

        } // if (AppliesToMe)
    } //for (auto id : root)
} //ParseNew

It just means you are passing a single char to a function while the function expects a pointer to a const char.

I assume it's this line

 strcpy(jsonString, *data);

Since data is a pointer and strcpy() expects a pointer as second parameter, you don't want to have the dereferencing asterisk (*) there.

You also don't want the ampersand ('&') there

    cp.ParseNew(&jsonString);

You either remove it or write cp.ParseNew(&jsonString[0]);.

If you want to pass a char array to a function you would normally pass the char* and the length of the array as second parameter.

I’ll throw in a plug for my library, which is designed to be particularly memory efficient. It easily handles 512 byte objects and works with much larger ones.

While you don’t need the feature for UDP, it’s specifically designed to handle parsing JSON objects that were split into multiple webhook response events.

4 Likes

Thanks @ScruffR… it really did just come down to removing the ‘*’ from the second parameter of strcpy. slap to the forehead. Now moving on to the parsing routine…

@rickkas7… I’ll check out your lib if I get strapped for memory. Reviewing the lib code, it didn’t seem like you can iterate through the elements using a loop. Do you have to retrieve each element by a known key? I’m sending commands to my devices with a variable number of elements/settings… so I think it would be preferable to loop through the received jsonPairs instead of calling each known key.

1 Like

If you're trying to be efficient with memory space, why use JSON at all? It's useful for sending (or receiving) data to third party services, because that's what a lot of them accept, but for your own communication between devices, why not use something more compact? Instead of,

why not something like,

"id:AAA;s:1351824120;d:48.756080,2.302038"

You might even be able to get rid of the id:, s:, and d: terms depending on your needs (as well as limiting the number of decimal places in your floats). You can easily create such strings using sprintf(), and decompose them using strtok() or strtok_r(), no libraies needed either creating or reading.

I come from the C# world and there JSON makes sense and memory constraints are not an issue. I though JSON would be ok for the photon and I already made up a bunch of single-character command/setting identifiers for my project. But then when testing the JSON parser, I am not happy that all the single characters have to be enclosed in double quotes which greatly inflates the size of the string. Thanks for the suggestion on strtok(). I only recently found cplusplus.com and will be referring there heavily.

I added two new functions to JsonParserGeneratorRK to allow you to iterate key/value pairs. There was an internal function to do it, but there’s now a wrapper than exposes it more easily.

/**
	 * Gets the key/value pair of an object by index
	 *
	 * @param container The object to look in (see getOuterKeyValueByIndex if you want to the outermost object you parsed)
	 *
	 * @param index 0 = first, 1 = second, ...
	 *
	 * @param key Filled in with the name of the key
	 *
	 * @param result Filled in with the value. The value can be of type: bool, int, unsigned long, float, double, String,
	 * or (char *, size_t&).
	 *
	 * @return true if the call succeeded or false if it failed.
	 *
	 * Normally you get a value in an object by its key, but if you want to iterate all of the keys you can
	 * use this method. Call it until it returns false.
	 *
	 * This should only be used for things like string, numbers, booleans, etc.. If you want to get a JSON array
	 * or object within an object, use getValueTokenByKey() instead.
	 */
	template<class T>
	bool getKeyValueByIndex(const JsonParserGeneratorRK::jsmntok_t *container, size_t index, String &key, T &result) const {
		const JsonParserGeneratorRK::jsmntok_t *keyToken;
		const JsonParserGeneratorRK::jsmntok_t *valueToken;

		if (getKeyValueTokenByIndex(container, keyToken, valueToken, index)) {
			getTokenValue(keyToken, key);
			return getTokenValue(valueToken, result);
		}
		else {
			return false;
		}
	}

	/**
	 * Gets the key/value pair of the outer object by index (0 = first, 1 = second, ...)
	 *
	 * Normally you get a value in an object by its key, but if you want to iterate all of the keys you can
	 * use this method.
	 *
	 * @param index 0 = first, 1 = second, ...
	 *
	 * @param key Filled in with the name of the key
	 *
	 * @param result Filled in with the value. The value can be of type: bool, int, unsigned long, float, double, String,
	 * or (char *, size_t&).
	 *
	 * @return true if the call succeeded or false if it failed.
	 *
	 * This should only be used for things like string, numbers, booleans, etc.. If you want to get a JSON array
	 * or object within an object, use getValueTokenByKey() instead.
	 */
	template<class T>
	bool getOuterKeyValueByIndex(size_t index, String &key, T &result) const {
		return getKeyValueByIndex(getOuterObject(), index, key, result);
	}
2 Likes