ArduinoJson icon indicating copy to clipboard operation
ArduinoJson copied to clipboard

JSONPath

Open cdaller opened this issue 7 years ago • 5 comments

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;
}

cdaller avatar Sep 28 '18 23:09 cdaller

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

bblanchon avatar Sep 29 '18 08:09 bblanchon

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);

cdaller avatar Sep 30 '18 09:09 cdaller

Hey @bblanchon

has this feature been added since or not?

omar-bb avatar Sep 21 '20 20:09 omar-bb

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.

bblanchon avatar Feb 24 '21 07:02 bblanchon

Please see https://github.com/bblanchon/ArduinoJson/issues/1904#issuecomment-1498614172 for another basic implementation that support member access via "parent.child". It's not following the JSON Path syntax, but it should be good enough for many projects.

bblanchon avatar Apr 06 '23 07:04 bblanchon