bblanchon / ArduinoJson

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

Can't convert custom class object to Json #2129

Open mairas opened 2 hours ago

mairas commented 2 hours ago

Describe the issue

I am working with numeric types that have a magic value indicating invalid/non-existing value (originating from NMEA 2000). When converting those to Signal K compatible Json, the invalid values need to be serialized to null.

I have created the following class template:

template <typename T>
class Nullable {
  public:
    Nullable() : value_{} {}
    Nullable(T value) : value_{value} {}

...

    bool is_valid() const {
      return value_ != invalid_value_;
    }

    static T invalid() {
      return invalid_value_;
    }

...

  private:
    T value_;
    static T invalid_value_;  // Defined using specializations for select types
};

I have also defined the following custom converter:

template <typename T>
void convertToJson(const Nullable<T> &src, JsonVariant dst) {
  if (src.is_valid()) {
    dst.set(src.value());
  } else {
    dst.clear();
  }
}

Elsewhere, I have this:

obj["elevation"] = value.elevation;  // Nullable<float>

I would expect obj["elevation"] to become either a number or null.

Troubleshooter report
Here is the report generated by the ArduinoJson Troubleshooter:

Troubleshooter does not have that particular error message:

In file included from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Polyfills/type_traits.hpp:15,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Polyfills/limits.hpp:7,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Memory/StringNode.hpp:11,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Memory/StringPool.hpp:8,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Memory/ResourceManager.hpp:8,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Variant/JsonVariantConst.hpp:10,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp:9,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Array/ElementProxy.hpp:7,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Array/JsonArray.hpp:7,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson.hpp:29,
                 from .pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson.h:9,
                 from /Users/mairas/src/SignalK/SensESP/src/sensesp/transforms/transform.h:6,
                 from /Users/mairas/src/SignalK/SensESP/src/sensesp/signalk/signalk_output.h:4,
                 from /Users/mairas/src/SensESP/NMEA0183/src/sensesp_nmea0183/data/gnss_data.h:4,
                 from /Users/mairas/src/SensESP/NMEA0183/src/sensesp_nmea0183/data/gnss_data.cpp:1:
.pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp: In instantiation of 'const bool ArduinoJson::V710PB22::detail::is_convertible<sensesp::Nullable<float>, long long int>::value':
.pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp:298:30:   recursively required by substitution of 'template<class T> struct ArduinoJson::V710PB22::Converter<T, typename ArduinoJson::V710PB22::detail::enable_if<ArduinoJson::V710PB22::detail::is_enum<T>::value, void>::type> [with T = sensesp::Nullable<float>]'
.pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp:298:30:   required from 'bool ArduinoJson::V710PB22::detail::VariantRefBase<TDerived>::doSet(T&&) const [with TConverter = ArduinoJson::V710PB22::Converter<sensesp::Nullable<float>, void>; T = const sensesp::Nullable<float>&; TDerived = ArduinoJson::V710PB22::detail::MemberProxy<ArduinoJson::V710PB22::JsonObject, const char*>]'
.pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp:80:44:   required from 'bool ArduinoJson::V710PB22::detail::VariantRefBase<TDerived>::set(const T&) const [with T = sensesp::Nullable<float>; TDerived = ArduinoJson::V710PB22::detail::MemberProxy<ArduinoJson::V710PB22::JsonObject, const char*>]'
.pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp:33:5:   required from 'ArduinoJson::V710PB22::detail::MemberProxy< <template-parameter-1-1>, <template-parameter-1-2> >& ArduinoJson::V710PB22::detail::MemberProxy< <template-parameter-1-1>, <template-parameter-1-2> >::operator=(const T&) [with T = sensesp::Nullable<float>; TUpstream = ArduinoJson::V710PB22::JsonObject; TStringRef = const char*]'
/Users/mairas/src/SensESP/NMEA0183/src/sensesp_nmea0183/data/gnss_data.cpp:46:28:   required from here
.pio/libdeps/esp32dev/ArduinoJson/src/ArduinoJson/Polyfills/type_traits/is_convertible.hpp:33:41: error: conversion from 'sensesp::Nullable<float>' to 'long long int' is ambiguous
   static const bool value = sizeof(probe(from_)) == sizeof(int);
                                   ~~~~~~^~~~~~~~

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

Reproduction

Provided above.

I'll create an isolated test case and provide it as a comment to this issue.

mairas commented 2 hours ago

A minimal test case using PlatformIO:

#include <Arduino.h>

#include "ArduinoJson.h"

template <typename T>
class Nullable {
 public:
  Nullable() : value_{} {}
  Nullable(T value) : value_{value} {}
  operator T() const { return value_; }

  operator T &() const { return value_; }

  bool is_valid() const { return value_ != invalid_value_; }

  T value() const { return value_; }

 private:
  T value_;
  static T invalid_value_;
};

template <> float Nullable<float>::invalid_value_ = std::numeric_limits<float>::lowest();

template <typename T>
void convertToJson(const Nullable<T> &src, JsonVariant dst) {
  if (src.is_valid()) {
    dst.set(src.value());
  } else {
    dst.clear();
  }
}

void setup() {
  Nullable<float> nullable_value = Nullable<float>{123.4};

  JsonDocument doc;

  doc["value"] = nullable_value;

  Serial.begin(115200);

  delay(100);

  serializeJson(doc, Serial);
}

void loop() {}
mairas commented 2 hours ago

Oh, and I also tried defining convertToJson for Nullable<float> directly (without the template), but that resulted in the same error.