bblanchon / ArduinoJson

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

How to update a parsed json field in ArduinoJson 6.12.0 ?? #1092

Closed noobGB closed 5 years ago

noobGB commented 5 years ago

Hi, I am using below code for serialization,

void WriteConfigJson() {
    //Serial.println("saving config");
   const size_t capacity = JSON_OBJECT_SIZE(7)+400;
   DynamicJsonDocument doc(capacity);

    // store LEDstates and blynk token to the json object
    doc["led1StateJson"] = led1State;
    doc["led2StateJson"] = led2State;
    doc["led3StateJson"] = led3State;
    doc["blynk_tokenJson"] = blynk_tokenJson;
    doc["server_idJson"] = server_id;
    doc["Wifi_SSID_Json"] = ssid;
    doc["Wifi_Pass_Json"] = password;

    //ESP.wdtFeed(); // feed ESP for its internal processing or it will reboot

    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
        //Serial.println("failed to open config file !!");      
    }

   serializeJson(doc, configFile);
   //delay(100);
   //serializeJson(doc, Serial);
    configFile.close();

}

and below code for deserialization,

        if (SPIFFS.begin()) {
        // Serial.println("mounted file system");
        if (SPIFFS.exists("/config.json")) {
            //file exists, reading and loading
            // Serial.println("reading config file");
            File configFile = SPIFFS.open("/config.json", "r");
            if (configFile) {
                //Serial.println("opened config file");             
                const size_t capacity = JSON_OBJECT_SIZE(7) + 400; //  + bytes suggested by ArduinoJson assistant
                DynamicJsonDocument doc(capacity);
                DeserializationError error = deserializeJson(doc, configFile);

                if (error) {
                    //Serial.print(F("deserializeJson() failed: "));
                }
        //Serial.print("deserializeJson() without error. ");

                    // Copy json values to respective variables from the file system

                    led1State = doc["led1StateJson"].as < int > ();
                    led2State = doc["led2StateJson"].as < int > ();
                    led3State = doc["led3StateJson"].as < int > ();
                    strlcpy(server_id, doc["server_idJson"] | "2", sizeof(server_id)); // keep the default "2" for blynk cloud server connect
                    strlcpy(ssid, doc["Wifi_SSID_Json"] | "N/A" , sizeof(ssid));        
                    strlcpy(password, doc["Wifi_Pass_Json"] | "N/A", sizeof(password));
                    strlcpy(blynk_tokenJson, doc["blynk_tokenJson"] | "N/A", sizeof(blynk_tokenJson));

                   // serializeJson(doc, Serial);
                } else {
                    // Serial.println("failed to load json config");
                }

                configFile.close();
            }
        }
    else {
        // Serial.println("failed to mount FS");
    }

I want to update and store some(one or multiple) json fields whenever the field value changes in my program. @bblanchon ,Please help me with the update process or I should serialize the full json object every time.

bblanchon commented 5 years ago

Hi @noobGB,

To update some fields of the JSON document, you must replace the values in the JsonDocument and call serializeJson() again. Don't forget to clear the File first; otherwise, the new JSON document will be appended to the old one.

Best Regards, Benoit

ewaldc commented 5 years ago

@noobGB, As you replace values, watch out for values that are larger in size than the original. This is not an issue for fixed size objects like integers, but definitely is for char */strings. e.g. if you have a string object "state" which could be "ON" or "OFF" and it's initial value is "ON" then obj["state"] = "OFF"; will not generate an error, but obj["state"] will serialize back as "state": "null" because ArduinoJson has no space in its document to fit the larger string. A shorter string will succeed (e.g. "OFF" as initial state turning to "ON"). To avoid this situation, cache the values for changing fields e.g. in item->state as in the following snippet:

if (strcmp(newstate, item->state)) { // state of type char* has changed 
    uint_fast16_t len = strlen(newstate) + 1;
    item->state = (char *)realloc(item->state, len);
    memcpy(item->state, newstate, len);
    item->obj[F("state")] = (const char*)item->state;
} 
SendJson(item->obj);  // serialize item object as send back to client via webserver

In this example I don't serialize the whole JSON object back, but just one "item". If objects are larger, you might consider optimizing the traffic e.g. between client and (web)server... Regards, Ewald

noobGB commented 5 years ago

Hi @bblanchon ,

Sorry I did not understand exactly, please check below code, if I call below function every time on any value update, would it be different from what you suggested or it would be a bad idea, please put some light into this,

void WriteConfigJson() {
    //Serial.println("saving config");
   const size_t capacity = JSON_OBJECT_SIZE(7)+400;
   DynamicJsonDocument doc(capacity);

    // store LEDstates and blynk token to the json object
    doc["led1StateJson"] = led1State;
    doc["led2StateJson"] = led2State;
    doc["led3StateJson"] = led3State;
    doc["blynk_tokenJson"] = blynk_tokenJson;
    doc["server_idJson"] = server_id;
    doc["Wifi_SSID_Json"] = ssid;
    doc["Wifi_Pass_Json"] = password;

    //ESP.wdtFeed(); // feed ESP for its internal processing or it will reboot

    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
        //Serial.println("failed to open config file !!");      
    }

   serializeJson(doc, configFile);
   //delay(100);
   //serializeJson(doc, Serial);
    configFile.close();

}

Thank you!!

ewaldc commented 5 years ago

@noobGB, at first sight this looks OK if the variables led1State etc. are in scope (e.g. global variables) when executing the function

noobGB commented 5 years ago

Yes @ewaldc, they are all global variables. Just for some clarification, what this function will do is, remove the existing json document( doc ) and create or serialize a new one every time ??

ewaldc commented 5 years ago

Since JSON document doc is local, it will be created, serialized and destroyed at each invocation.

What I meant with my earlier comment is that if you keep doc alive for longer periods of time and modify it as your led states change, something like doc["led3StateJson"] = F("OFF"); will fail if doc["led3StateJson"] was less than 3 characters (e.g. "ON"). It will serialize as "led3StateJson" : null A typical example where such error could occur:

boolean led1State;  // global variable
doc["led1State"] =  (led1State)  ? F("ON") : F("OFF"); 

However, with a global variable of type char* like in your example and _doc being recreated all the time, it's OK since ArduinoJson will simply store the pointer to your string, hence why it needs to stay in scope - in your example, until the json object is written to SPIFFS.

bblanchon commented 5 years ago

@noobGB, if you want to update one field in a file, you have to:

  1. deserialize the file into a JsonDocument (local, most likely)
  2. update the field
  3. truncate the file
  4. serialize the JsonDocument to the file
DynamicJsonDocument doc(4096);

File file = SPIFFS.open("/config.json", "r");
deserializeJson(doc, file);
file.close();

doc["key"] = "value";

file = SPIFFS.open("/config.json", "w");
serializeJson(doc, file);
file.close();

BTW, there is a detailed case study of ArduinoJson with SPIFFS in Mastering ArduinoJson.

noobGB commented 5 years ago

Hi @bblanchon ,

Thank you, I will try and get back to you here.

noobGB commented 5 years ago

Hi @bblanchon ,

I have tried suggested code and it is working fine, thank you!!