bblanchon / ArduinoJson

📟 JSON library for Arduino and embedded C++. Simple and efficient.
https://arduinojson.org
MIT License
6.7k stars 1.12k forks source link

Adding entry in JsonObject using File object causes corrupted JSON #2077

Closed AcuarioCat closed 5 months ago

AcuarioCat commented 5 months ago

I need to create a json array using the file directory listing from an SD card. When adding the file path to the JsonObject I end up with a corrupted json.

Strangely serializing the object that is being added shows the correct JsonObject but when added in the array it is corrupt.

If I print the file name to a char then add it the resulting json is fine. Following is my code that shows the problem and temporary solution.

#include <SD.h>
#define SD_CS         27

File dir;

 if (!SD.begin(SD_CS))
    Serial.println("SD failed to start");
else
    {
    dir = SD.open(audioDir);
    listDir(SD, audioDir, 1);
        }

/////////////////////////////////  For SD card  ///////////////////////////////////////////////////////////
void listDir(fs::FS& fs, const char* dirname, uint8_t levels) {
    Serial.printf("Listing directory: %s\n", dirname);
    String jsonString;
    int8_t fileNumber = 0;
    char trackPath[100];

    File root = fs.open(dirname);
    if (!root) {
        Serial.println("Failed to open directory");
        return;
    }
    if (!root.isDirectory()) {
        Serial.println("Not a directory");
        return;
    }

    // Add values in the object
    JsonDocument doc;
    doc["playing"] = 1;
    JsonArray data = doc["data"].to<JsonArray>();

    File file = root.openNextFile();
    while (file) {
        if (file.isDirectory()) {
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if (levels) {
                listDir(fs, file.path(), levels - 1);
            }
        }
        else {
            JsonObject jdata = data.add<JsonObject>();
            jdata["n"] = fileNumber;

            jdata["t"] = file.path();                                               // <<< Corrupted JSON

            //snprintf(trackPath, sizeof(trackPath), file.path());   //  <<<<
            //jdata["t"] =trackPath;                                            //  <<<< JSON OK

            serializeJson(jdata, Serial);
            Serial.println();
            fileNumber++;

        }
        file = root.openNextFile();
    }

    doc.shrinkToFit();  // optional

    serializeJson(doc, jsonString);
    Serial.println(jsonString);
    Serial.println();
    root.close();
    file.close();
}

The corrupted output shows the following:

SD ok
Listing directory: /mp3
{"n":0,"t":"/mp3/16 And On.mp3"}
{"n":1,"t":"/mp3/01 Midas Touch (7' Pop Edit).mp3"}
{"n":2,"t":"/mp3/02 And The Beat Goes On.mp3"}
{"n":3,"t":"/mp3/03 Take That To The Bank (M&M Mix).mp3"}
{"n":4,"t":"/mp3/04 Romeo Where's Juliet-.mp3"}
{"n":5,"t":"/mp3/05 Wet My Whistle.mp3"}
{"n":6,"t":"/mp3/06 It's A Love Thing.mp3"}
{"n":7,"t":"/mp3/07 There It Is.mp3"}
{"n":8,"t":"/mp3/08 Get In Touch With Me.mp3"}
{"n":9,"t":"/mp3/09 Headlines.mp3"}
{"n":10,"t":"/mp3/10 A Night To Remember (The M&M Mix).mp3"}
{"n":11,"t":"/mp3/11 Sweet Sensation.mp3"}
{"n":12,"t":"/mp3/12 Operator (Edited Version).mp3"}
{"n":13,"t":"/mp3/13 Over And Over.mp3"}
{"n":14,"t":"/mp3/14 Some Kinda Lover.mp3"}
{"n":15,"t":"/mp3/15 And The Mix Goes On.mp3"}
{"playing":1,"data":[{"n":0,"t":"/mp3/15 And The Mix Goes On.mp3"},{"n":1,"t":"�������?���?"},{"n":2,"t":""},{"n":3,"t":"/mp3/14 Some Kinda Lover.mp3"},{"n":4,"t":"s On.mp3\"},{\"n\":1,\"t\":\"�������?���?\"},{\"n\":2,\"t\":\"\"},{\"n\":3,\"t\":\"/mp3/14 Some Kinda Lover.mp3\"},{\""},{"n":5,"t":"r.mp3\\\""},{"n":6,"t":""},{"n":7,"t":"\"t\":\"s On.mp3\\\"},{\\\"n\\\":1,\\\"t\\\":\\\"�������?���?\\\"},{\\\"n\\\":2,\\\"t\\\":\\\"\\\"},{\\\"n\\\":3,\\\"t\\\":\\\"/mp3/14 Some Kinda Lover.mp3\\\"},{\\\"\"},{\"n\":5,\"t\":\"r.mp3\\\\\\\"\"}"},{"n":8,"t":""},{"n":9,"t":"\"t\":\"s On.mp3\\\"},{\\\"n\\\":1,\\\"t\\\":\\\"�������?���?\\\"},{\\\"n\\\":2,\\\"t\\\":\\\"\\\"},{\\\"n\\\":3,\\\"t\\\":\\\"/mp3/14 Some Kinda Lover.mp3\\\"},{\\\"\"},{\"n\":5,\"t\":\"r.mp3\\\\\\\"\"}"},{"n":10,"t":""},{"n":11,"t":"\"},{\\\"n\\\":2,\\\"t\\\":\\\"\\\"},{\\\"n\\\":3,\\\"t\\\":\\\"/mp3/14 Some Kinda Lover.mp3\\\"},{\\\"\"},{\"n\":5,\"t\":\"r.mp3\\\\\\\"\"}"},{"n":12,"t":"/mp3/14 Some Kinda Lover.mp3"},{"n":13,"t":",\"t\":\"/mp3/14 Some Kinda Lover.mp3\"},{\"n\":4,\"t\":\"s On.mp3\\\"},{\\\"n\\\":1,\\\"t\\\":\\\"�������?���?\\\"},{\\\"n\\\":2,\\\"t\\\":\\\"\\\"},{\\\"n\\\":3,\\\"t\\\":\\\"/mp3/14 Some Kinda Lover.mp3\\\"},{\\\"\"},{\"n\":5,\"t\":\"r.mp3\\\\\\\"\"}"},{"n":14,"t":"/mp3/14 Some Kinda Lover.mp3"},{"n":15,"t":"\":2,\\\"t\\\":\\\"\\\"},{\\\"n\\\":3,\\\"t\\\":\\\"/mp3/14 Some Kinda Lover.mp3\\\"},{\\\"\"},{\"n\":5,\"t\":\"r.mp3\\\\\\\"\"}"}]}

Changing to the working code gives the following correct JSON

SD ok
Listing directory: /mp3
{"n":0,"t":"/mp3/16 And On.mp3"}
{"n":1,"t":"/mp3/01 Midas Touch (7' Pop Edit).mp3"}
{"n":2,"t":"/mp3/02 And The Beat Goes On.mp3"}
{"n":3,"t":"/mp3/03 Take That To The Bank (M&M Mix).mp3"}
{"n":4,"t":"/mp3/04 Romeo Where's Juliet-.mp3"}
{"n":5,"t":"/mp3/05 Wet My Whistle.mp3"}
{"n":6,"t":"/mp3/06 It's A Love Thing.mp3"}
{"n":7,"t":"/mp3/07 There It Is.mp3"}
{"n":8,"t":"/mp3/08 Get In Touch With Me.mp3"}
{"n":9,"t":"/mp3/09 Headlines.mp3"}
{"n":10,"t":"/mp3/10 A Night To Remember (The M&M Mix).mp3"}
{"n":11,"t":"/mp3/11 Sweet Sensation.mp3"}
{"n":12,"t":"/mp3/12 Operator (Edited Version).mp3"}
{"n":13,"t":"/mp3/13 Over And Over.mp3"}
{"n":14,"t":"/mp3/14 Some Kinda Lover.mp3"}
{"n":15,"t":"/mp3/15 And The Mix Goes On.mp3"}
{"playing":1,"data":[{"n":0,"t":"/mp3/16 And On.mp3"},{"n":1,"t":"/mp3/01 Midas Touch (7' Pop Edit).mp3"},{"n":2,"t":"/mp3/02 And The Beat Goes On.mp3"},{"n":3,"t":"/mp3/03 Take That To The Bank (M&M Mix).mp3"},{"n":4,"t":"/mp3/04 Romeo Where's Juliet-.mp3"},{"n":5,"t":"/mp3/05 Wet My Whistle.mp3"},{"n":6,"t":"/mp3/06 It's A Love Thing.mp3"},{"n":7,"t":"/mp3/07 There It Is.mp3"},{"n":8,"t":"/mp3/08 Get In Touch With Me.mp3"},{"n":9,"t":"/mp3/09 Headlines.mp3"},{"n":10,"t":"/mp3/10 A Night To Remember (The M&M Mix).mp3"},{"n":11,"t":"/mp3/11 Sweet Sensation.mp3"},{"n":12,"t":"/mp3/12 Operator (Edited Version).mp3"},{"n":13,"t":"/mp3/13 Over And Over.mp3"},{"n":14,"t":"/mp3/14 Some Kinda Lover.mp3"},{"n":15,"t":"/mp3/15 And The Mix Goes On.mp3"}]}
bblanchon commented 5 months ago

Hi @AcuarioCat,

file.path() returns a const char*, and JsonDocument stores const char* as a pointer and not as a copy like other string types.

The rationale behind this design is that const char* is often used to point to a string literal, which is a read-only memory block. In this case, it's safe to store the pointer directly in the JsonDocument. Unfortunately, in your case, the const char* points to a temporary string, so it's not safe to store by pointer.

The solution to your problem is to pass any other type of string. For example, you could cast the const char* to a char* like so:

- jdata["t"] = file.path();
+ jdata["t"] = const_cast<char*>(file.path());

I know it's very unpleasant to use const_cast, but fortunately, it's safe in this case because JsonDocument will not modify the data pointed by the pointer.

If you prefer avoiding the const_cast, you can use a String instead, like so:

- jdata["t"] = file.path();
+ jdata["t"] = String(file.path());

This is clearly a design flaw in the library, and I hope to eliminate it someday.

Best regards, Benoit