Arduinojson: add json path functionality to extract values

Created on 29 Sep 2018  路  4Comments  路  Source: bblanchon/ArduinoJson

I have an application that fetches a json file from an http server and extracts a value from the json. The url (and therefore the structure of the json) is configurable during runtime. So I wanted to use a json path to define which value to extract from the json content. I googled, but could not find any, so I wrote a very simple jsonPath parser.

I think a method like this would be quite useful in the library - what do you think?

As I am not very experienced in C++, the method could (and probably should) be rewritten before being integrated :-)

// parse jsonPaths like $.foo[1].bar.baz[2][3].value (equals to foo[1].bar.baz[2][3].value)
float parseJson(char* jsonString, char *jsonPath) {
    float jsonValue;
    DynamicJsonBuffer jsonBuffer;

    JsonVariant root = jsonBuffer.parse(jsonString);
    JsonVariant element = root;

    if (root.success()) {
        // parse jsonPath and navigate through json object:
        char pathElement[40];
        int pathIndex = 0;

        printf("parsing '%s'\n", jsonPath);
        for (int i = 0; jsonPath[i] != '\0'; i++){
            if (jsonPath[i] == '$') {
                element = root;
            } else if (jsonPath[i] == '.') {
                if (pathIndex > 0) {
                    pathElement[pathIndex++] = '\0';
                    // printf("pathElement '%s'\n", pathElement);
                    pathIndex = 0;
                    element = element[pathElement];
                    if (!element.success()) {
                        printf("failed to parse key %s\n", pathElement);
                    }
                }
            } else if ((jsonPath[i] >= 'a' && jsonPath[i] <= 'z') 
                    || (jsonPath[i] >= 'A' && jsonPath[i] <= 'Z') 
                    || (jsonPath[i] >= '0' && jsonPath[i] <= '9')
                    || jsonPath[i] == '-' || jsonPath[i] == '_'
                    ) {
                pathElement[pathIndex++] = jsonPath[i];
            } else if (jsonPath[i] == '[') {
                if (pathIndex > 0) {
                    pathElement[pathIndex++] = '\0';
                    // printf("pathElement '%s'\n", pathElement);
                    pathIndex = 0;
                    element = element[pathElement];
                    if (!element.success()) {
                        printf("failed in parsing key %s\n", pathElement);
                    }
                }
            } else if (jsonPath[i] == ']') {
                pathElement[pathIndex++] = '\0';
                int arrayIndex = strtod(pathElement, NULL);
                // printf("index '%s' = %d\n", pathElement, arrayIndex);
                pathIndex = 0;
                element = element[arrayIndex];
                if (!element.success()) {
                    printf("failed in parsing index %d\n", arrayIndex);
                }
            }
        }  
        // final token if any:
        if (pathIndex > 0) {
            pathElement[pathIndex++] = '\0';
            // printf("pathElement '%s'\n", pathElement);
            pathIndex = 0;
            element = element[pathElement];
            if (!element.success()) {
                printf("failed in parsing key %s\n", pathElement);
            }
        }

        jsonValue = element.as<float>();

        //jsonValue = measurements[1]["sensordatavalues"][0]["value"];
        printf("success reading value: %f\n", jsonValue);
    } else {
        jsonValue = -1; // should be NaN or something similar
        printf("could not parse json for value");
    }
    return jsonValue;
}
enhancement

Most helpful comment

Hi @cdaller,

I like this idea; I think it would be a great feature for the library.
I'm adding this feature to the to-do list.
However, I'm quite busy with other features of v6 right now, so please be patient.

BTW, I think a signature like the following would offer greater flexibility:

JsonVariant resolve(JsonVariant, const char* path);

Regards,
Benoit

All 4 comments

Hi @cdaller,

I like this idea; I think it would be a great feature for the library.
I'm adding this feature to the to-do list.
However, I'm quite busy with other features of v6 right now, so please be patient.

BTW, I think a signature like the following would offer greater flexibility:

JsonVariant resolve(JsonVariant, const char* path);

Regards,
Benoit

Another optimization: using a JsonPath object that does the parsing of the path only once, so mulitple extraction of the data of the same path will be more efficient. I think, this is a common behaviour - using the same path again and again.

JsonPath parser should extract the key names and array indices, so resolving it to a value will become more efficient.

Future parsers might support filter expressions or other features from http://goessner.net/articles/JsonPath/ - maybe we should have a look at https://github.com/danielaparker/jsoncons/blob/master/doc/ref/jsonpath/jsonpath.md

// simple usage (not so efficient)
JsonVariant resolve(JsonVariant, const char* path);

// more efficient usage (optimize parsing)
JsonVariant resolve(JsonVariant, JsonPath);

Hey @bblanchon

has this feature been added since or not?

Not exactly a JSON Path feature, but the function presented in https://github.com/bblanchon/ArduinoJson/issues/1505#issuecomment-782825946 can be used as a compile-time alternative.

Was this page helpful?
0 / 5 - 0 ratings