Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to cache a JsonObject between function calls? #2067

Closed
scottchiefbaker opened this issue Mar 13, 2024 · 5 comments
Closed

Is there a way to cache a JsonObject between function calls? #2067

scottchiefbaker opened this issue Mar 13, 2024 · 5 comments
Labels
question v7 ArduinoJson 7

Comments

@scottchiefbaker
Copy link

scottchiefbaker commented Mar 13, 2024

I have Googled, looked in closed issues, and I even hit up ChatGPT and I cannot find an answer to what I assume is a simple request.

Describe the issue
I need a way to cache a JsonObject between function calls to reuse if a request is made too quickly

Environment
Here is the environment that I'm using':

  • Microconroller: Wemos D1 Mini
  • Core/runtime: ESP8266 core for Arduino v3.1.2
  • IDE: arduino-cli v0.35.3

Reproduction
Here is a small snippet that demonstrate the problem.

#include "ArduinoJson.h"

void foobar(JsonObject obj) {
    static JsonObject last_obj;
    static uint32_t last_hit = 0;
    bool too_soon            = (millis() - last_hit) < 5000;

    if (too_soon) {
        // Use the last cached hit somehow?
        obj.set(last_obj);
    } else {
        obj["name"]  = "Jason Doolis";
        obj["color"] = "Red";

        last_hit = millis();
        last_obj.set(obj); // Cache the obj for next time?
    }
}

void setup() {
    Serial.begin(115200);
}

void loop() {
    JsonDocument json;

    JsonObject xx = json["test"].to<JsonObject>();
    foobar(xx);

    serializeJson(json, Serial);
    Serial.println();

    delay(2000);
}

Expected output:
The JSON uses the previous iteration as a cache

Actual output:
When I call this function every two seconds I see this:

{"test":{"name":"Jason Doolis","color":"Red"}}
{"test":{}}
{"test":{}}
{"test":{"name":"Jason Doolis","color":"Red"}}
{"test":{}}
{"test":{}}

Clearly it's getting the correct data the first time, but when it comes back and triggers the too_soon it's not seeing the data from the previous iteration in last_obj. Bonus points if I could append "cached": true to the object after realize we're in the too_soon if statement.

@bblanchon
Copy link
Owner

Hi @scottchiefbaker,

JsonObject is a reference to an object in a JsonDocument.
In other words, JsonObject doesn't hold any data but points to the data in the JsonDocument.

To establish this link from the JsonObject to the JsonDocument, you must assign the reference to something that exists in the document; otherwise, the reference will be unbound.

In your case, last_obj is unbound because it is never assigned.
Sure, the program calls JsonObject::set(), but it's a no-op because the reference is unbound.

One solution could be to assign last_obj, like so:

- last_obj.set(obj);
+ last_obj = obj;

But I suspect that caching the reference is not what you are after.
Instead, you probably expect to save the content of the object pointed by obj.
In that case, the solution is to use a JsonDocument instead:

- static JsonObject last_obj;
+ static JsonDocument last_obj;

Best regards,
Benoit

@bblanchon bblanchon added the v7 ArduinoJson 7 label Mar 15, 2024
@scottchiefbaker
Copy link
Author

You are correct, I want the content of the object. When I change my static variable to a Document like you mention I now get an error:

no matching function for call to 'ArduinoJson::V703L1::JsonObject::set(ArduinoJson::V703L1::JsonDocument&)'

I also tried using equals assignment and I get:

no match for 'operator=' (operand types are 'ArduinoJson::V703L1::JsonObject' and 'ArduinoJson::V703L1::JsonDocument')

I think we're on the right track, but I don't know how to get the data back into the original JsonObject

@bblanchon
Copy link
Owner

Indeed, JsonObject::set() only accepts JsonObjectConst, and there is no implicit conversion between JsonDocument and JsonObjectConst. I should probably improve that, but I never noticed it because very few people use JsonObject::set().

You can workaround this error by explicitly converting the JsonDocument to a JsonObject:

- obj.set(last_obj);
+ obj.set(last_obj.as<JsonObject>());

Alternatively, you could refactor your program so that foobar() returns a JsonDocument:

JsonDocument foobar() {
    static JsonDocument doc;
    static uint32_t last_hit = 0;
    bool too_soon = (millis() - last_hit) < 5000;

    if (!too_soon) {
        doc["name"]  = "Jason Doolis";
        doc["color"] = "Red";
        last_hit = millis();
    }

    return last_obj;
}

void loop() {
    JsonDocument json;
    json["test"] = foobar();

    serializeJson(json, Serial);
    Serial.println();

    delay(2000);
}

Sure, it produces one or two additional copies, but it's a thousand times more readable.

Another alternative would be to return a String from foobar and then use serialized() to insert it in the main document.

@scottchiefbaker
Copy link
Author

Aha! This is exactly what I needed. I didn't realize you could put a JsonDocument into another JsonDocument. That really simplifies things. With this method I was able to include "cached": true as well.

There is a minor typo in your code above with last_obj so here is my full working code for posterity in case someone else has a similar problem:

#include "ArduinoJson.h"

void setup() {
  Serial.begin(115200);
}

JsonDocument foobar() {
  static JsonDocument doc;
    
  static uint32_t last_hit = 0;
  int32_t diff             = millis() - last_hit;
  bool too_soon            = abs(diff) < 5000;

  if (last_hit && too_soon) {
    doc["cached"] = true;
  } else {
    doc.clear();
    doc["name"] = "Jason Doolis";
    doc["time"] = millis();

    last_hit = millis();
  }

  return doc;
}

void loop() {
  JsonDocument json;
  json["test"] = foobar();

  serializeJson(json, Serial);
  Serial.println();

  delay(2000);
}

Thank you for your assistance on this. And thank you for an AMAZING library. ArduinoJson is a masterpiece.

@bblanchon
Copy link
Owner

You're welcome, @scottchiefbaker!
Thank you for using ArduinoJson.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question v7 ArduinoJson 7
Projects
None yet
Development

No branches or pull requests

2 participants