bblanchon / ArduinoJson

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

Question: how to use JsonObject as class variable (v5) #1484

Closed juliovedovatto closed 3 years ago

juliovedovatto commented 3 years ago

I was trying to build a Storage class for my application, relying on LittleFS and ArduinoJson.

Basically:

Basically, something like this:

#include <Arduino.h>
#include <ArduinoJson.h>
#include <LittleFS.h>

class Storage {
  public:
    Storage() {
      this->setup();
      this->load();
    }

    template <typename T>
    void set(char* key, T value) {
      this->root[key] = value;
      this->save();
    }

    template <typename T>
    auto get(char* key, T defaultValue = nullptr) {
      if (!this->root.containsKey(key)) {
        this->load();
      }

      return this->root.containsKey(key) ? this->root[key] : defaultValue;
    }

    auto get(char* key) {
      return this->get(key, nullptr);
    }

    void remove(char* key) {
      this->root.remove(key);
      this->save();
    }

  private:
    DynamicJsonBuffer jsonBuffer;
    JsonObject &root = jsonBuffer.createObject();

    void setup() {
      LittleFS.begin();

      if (!LittleFS.exists('application.json')) {
        File file = LittleFS.open(Store::storeFile, "w+");
        file.close();
      }

      DynamicJsonBuffer buffer;
      this->root = this->jsonBuffer.createObject();
    }

    void save() {
      File file = LittleFS.open(Store::storeFile, "w");

      if (this->root.printTo(file) == 0) {
        // TODO: show error message
      }
      file.close();
    }

    void load() {
      File file = LittleFS.open(Store::storeFile, "r");

      DynamicJsonBuffer buffer;
      this->root = buffer.parseObject(file);

      if (!this->root.success()) {
        // TODO: show error message
      }

      file.close();
    }
};

But I'm getting the following error on this line:

this->root = this->jsonBuffer.parseObject(file);

Error:

function "ArduinoJson::JsonObject::operator=(const ArduinoJson::JsonObject &)" (declared implicitly) cannot be referenced -- it is a deleted function

Since me C++ knowledge is kinda limited, I'm not able to solve this issue. Is there any approach that I can do for this case @bblanchon?

PS: I'm not able to use v6 due hardware limitations.

bblanchon commented 3 years ago

Hi @juliovedovatto,

Indeed, in C++, you cannot rebind a reference, so you'll have to use pointers all the way. That was the main design flaw in v5, and that's why v6 came to be.

You say, "I'm not able to use v6 due to hardware limitations". This comes as a big surprise to me; can you elaborate?

Best regards, Benoit

bblanchon commented 3 years ago

PS: I just re-read the case-study from the v5 edition of Mastering ArduinoJson (section 7.4.6) and it reminded me that the trick was to use a JsonVariant instead of a JsonObject&.

juliovedovatto commented 3 years ago

@bblanchon I was getting some many error when building, that I assumed that would be because I'm limited by hardware to use C++11 (ESP 8266). So I downgraded to v5.

I refactored all over again, to use v6 instead:

#include <Arduino.h>
#include <ArduinoJson.h>
#include <LittleFS.h>

#include "../config/Application.h"

class Storage
{
public:
  Storage()
  {
    this->setup();
    this->load();
  }

  template <typename TString, typename TAny, typename R>
  R get(TString* key, TAny* defaultValue)
  {
    if (!this->doc.containsKey(key)){
      this->load();
    }

    return this->doc.containsKey(key) ? this->doc[key] : defaultValue;
  }

  template <typename T>
  void set(char *key, T value)
  {
    this->doc[key] = value;
    this->save();
  }

  template <typename TChar>
  void remove(char *key)
  {
    this->doc.remove(key);
    this->save();
  }

private:
  DynamicJsonDocument doc = DynamicJsonDocument(1024);

  void setup()
  {
    LittleFS.begin();

    if (!LittleFS.exists(Store::storeFile))
    {
      File file = LittleFS.open(Store::storeFile, "w+");
      file.close();
    }
  }

  void save()
  {
    File file = LittleFS.open(Store::storeFile, "w");

    if (serializeJson(doc, file) == 0) {
      // TODO: show error message
    }
    file.close();
  }

  void load() {
    File file = LittleFS.open(Store::storeFile, "r");
    DeserializationError error = deserializeJson(doc, file);

    if (error) {
      // TODO: show error message
    }

    file.close();
  }
};

But when I try to recover a value from my Storage:

setup() {
  // [...] 
  char* swtichState = storage->get<char, char, char*>("switch", "OFF");
  if (strcmp(swtichState, "ON")) {
  // [...]
  }
  // [...]
}

But I'm getting the following error:

src/main.cpp:64:70: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
   char* swtichState = storage->get<char, char, char*>("switch", "OFF");
                                                                      ^
src/main.cpp:64:70: warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]
In file included from .pio/libdeps/development/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp:14:0,
                 from .pio/libdeps/development/ArduinoJson/src/ArduinoJson/Array/ArrayIterator.hpp:8,
                 from .pio/libdeps/development/ArduinoJson/src/ArduinoJson/Array/ArrayRef.hpp:8,
                 from .pio/libdeps/development/ArduinoJson/src/ArduinoJson.hpp:17,
                 from .pio/libdeps/development/ArduinoJson/src/ArduinoJson.h:9,
                 from src/core/storage/main.h:2,
                 from src/main.cpp:7:
.pio/libdeps/development/ArduinoJson/src/ArduinoJson/Variant/VariantAs.hpp: In instantiation of 'T ArduinoJson6172_91::variantAs(ArduinoJson6172_91::VariantData*, ArduinoJson6172_91::MemoryPool*) [with T = char*]':
.pio/libdeps/development/ArduinoJson/src/ArduinoJson/Variant/VariantRef.hpp:259:37:   required from 'ArduinoJson6172_91::VariantRef::operator T() const [with T = char*]'
.pio/libdeps/development/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp:77:30:   required from 'ArduinoJson6172_91::MemberProxy<TParent, TStringRef>::operator T() const [with T = char*; TObject = ArduinoJson6172_91::JsonDocument&; TStringRef = char*]'
src/core/storage/main.h:23:39:   required from 'R Storage::get(TString*, TAny*) [with TString = char; TAny = char; R = char*]'
src/main.cpp:64:70:   required from here
.pio/libdeps/development/ArduinoJson/src/ArduinoJson/Variant/VariantAs.hpp:94:27: error: invalid conversion from 'ArduinoJson6172_91::enable_if<true, const char*>::type {aka const char*}' to 'char*' [-fpermissive]
   return variantAs<T>(data);

My intention is to create a generic get method, to retrieve anything from store (scalar types, arrays or maps). Case it fails, return nullptr (or other nullish type) . But I'm failing to create such method using ArduinoJSON.

Could you enlighten this situation?

bblanchon commented 3 years ago

Please see invalid conversion from 'const char' to 'char' [-fpermissive].