Simplified JsonVariant::as<T>() to always return T

This commit is contained in:
Benoit Blanchon
2021-03-08 09:58:09 +01:00
parent 9094179856
commit e4ce75e20c
16 changed files with 78 additions and 81 deletions

View File

@ -11,9 +11,12 @@ HEAD
* Added `JsonVariant::is<JsonArrayConst/JsonObjectConst>()` (issue #1412)
* Added `JsonVariant::is<JsonVariant/JsonVariantConst>()` (issue #1412)
* Changed `JsonVariantConst::is<JsonArray/JsonObject>()` to return `false` (issue #1412)
* Simplified `JsonVariant::as<T>()` to always return `T` (see below)
> ### BREAKING CHANGES
>
> #### Support for `char` removed
>
> We cannot cast a `JsonVariant` to a `char` anymore, so the following will break:
> ```c++
> char age = doc["age"]; // error: no matching function for call to 'variantAs(VariantData*&)'
@ -34,13 +37,39 @@ HEAD
> doc["age"] = age; // OK
> ```
>
> #### `as<T>()` always returns `T`
>
> Previously, `JsonVariant::as<T>()` could return a type different from `T`.
> The most common example is `as<char*>()` that returned a `const char*`.
> While this feature simplified a few use cases, it was confusing and complicated the
> implementation of custom converters.
>
> Starting from this version, `as<T>` doesn't try to auto-correct the return type and always return `T`,
> which means that you cannot write this anymore:
>
> ```c++
> Serial.println(doc["sensor"].as<char*>()); // error: invalid conversion from 'const char*' to 'char*' [-fpermissive]
> ```
>
> Instead, you must write:
>
> ```c++
> Serial.println(doc["sensor"].as<const char*>()); // OK
> ```
>
>
> #### `DeserializationError::NotSupported` removed
>
> On a different topic, `DeserializationError::NotSupported` has been removed.
> Instead of returning this error:
>
> * `deserializeJson()` leaves `\uXXXX` unchanged (only when `ARDUINOJSON_DECODE_UNICODE` is `0`)
> * `deserializeMsgPack()` replaces unsupported values with `null`s
>
> Lastly, a very minor change conserns `JsonVariantConst::is<T>()`.
> #### Const-aware `is<T>()`
>
> Lastly, a very minor change concerns `JsonVariantConst::is<T>()`.
> It used to return `true` for `JsonArray` and `JsonOject`, but now it returns `false`.
> Instead, you must use `JsonArrayConst` and `JsonObjectConst`.

View File

@ -92,7 +92,7 @@ void setup() {
// Extract values
Serial.println(F("Response:"));
Serial.println(doc["sensor"].as<char*>());
Serial.println(doc["sensor"].as<const char*>());
Serial.println(doc["time"].as<long>());
Serial.println(doc["data"][0].as<float>(), 6);
Serial.println(doc["data"][1].as<float>(), 6);

View File

@ -60,7 +60,6 @@ TEST_CASE("JsonArray::operator[]") {
array[0] = str;
REQUIRE(str == array[0].as<const char*>());
REQUIRE(str == array[0].as<char*>()); // <- short hand
REQUIRE(true == array[0].is<const char*>());
REQUIRE(false == array[0].is<int>());
}

View File

@ -99,8 +99,8 @@ TEST_CASE("deserialize JSON array") {
REQUIRE(err == DeserializationError::Ok);
REQUIRE(2 == arr.size());
REQUIRE(arr[0].as<char*>() == 0);
REQUIRE(arr[1].as<char*>() == 0);
REQUIRE(arr[0].as<const char*>() == 0);
REQUIRE(arr[1].as<const char*>() == 0);
}
}

View File

@ -182,8 +182,8 @@ TEST_CASE("deserialize JSON object") {
REQUIRE(err == DeserializationError::Ok);
REQUIRE(doc.is<JsonObject>());
REQUIRE(obj.size() == 2);
REQUIRE(obj["key1"].as<char*>() == 0);
REQUIRE(obj["key2"].as<char*>() == 0);
REQUIRE(obj["key1"].as<const char*>() == 0);
REQUIRE(obj["key2"].as<const char*>() == 0);
}
SECTION("Array") {

View File

@ -48,7 +48,6 @@ TEST_CASE("JsonObject::operator[]") {
REQUIRE(true == obj["hello"].is<const char*>());
REQUIRE(false == obj["hello"].is<long>());
REQUIRE(std::string("h3110") == obj["hello"].as<const char*>());
REQUIRE(std::string("h3110") == obj["hello"].as<char*>()); // <- short hand
}
SECTION("array") {
@ -189,7 +188,7 @@ TEST_CASE("JsonObject::operator[]") {
obj["hello"] = vla;
REQUIRE(std::string("world") == obj["hello"].as<char*>());
REQUIRE(std::string("world") == obj["hello"].as<const char*>());
}
SECTION("obj.set(VLA, str)") {
@ -209,7 +208,7 @@ TEST_CASE("JsonObject::operator[]") {
obj["hello"].set(vla);
REQUIRE(std::string("world") == obj["hello"].as<char*>());
REQUIRE(std::string("world") == obj["hello"].as<const char*>());
}
SECTION("obj[VLA]") {

View File

@ -22,7 +22,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(false == variant.as<bool>());
REQUIRE(0 == variant.as<int>());
REQUIRE(0.0f == variant.as<float>());
REQUIRE(0 == variant.as<char*>());
REQUIRE(0 == variant.as<const char*>());
REQUIRE("null" == variant.as<std::string>());
}
@ -104,7 +104,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<bool>() == true);
REQUIRE(variant.as<long>() == 0L);
REQUIRE(variant.as<const char*>() == std::string("hello"));
REQUIRE(variant.as<char*>() == std::string("hello"));
REQUIRE(variant.as<const char*>() == std::string("hello"));
REQUIRE(variant.as<std::string>() == std::string("hello"));
}
@ -114,7 +114,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(variant.as<bool>() == true);
REQUIRE(variant.as<long>() == 4L);
REQUIRE(variant.as<double>() == 4.2);
REQUIRE(variant.as<char*>() == std::string("4.2"));
REQUIRE(variant.as<const char*>() == std::string("4.2"));
REQUIRE(variant.as<std::string>() == std::string("4.2"));
}
@ -211,8 +211,7 @@ TEST_CASE("JsonVariant::as()") {
REQUIRE(cvar.as<bool>() == true);
REQUIRE(cvar.as<long>() == 0L);
REQUIRE(cvar.as<const char*>() == std::string("hello"));
REQUIRE(cvar.as<char*>() == std::string("hello"));
// REQUIRE(cvar.as<std::string>() == std::string("hello"));
REQUIRE(cvar.as<std::string>() == std::string("hello"));
}
SECTION("as<enum>()") {

View File

@ -17,8 +17,8 @@ TEST_CASE("JsonVariant undefined") {
REQUIRE(variant.as<unsigned>() == 0);
}
SECTION("char*") {
REQUIRE(variant.as<char*>() == 0);
SECTION("const char*") {
REQUIRE(variant.as<const char*>() == 0);
}
SECTION("double") {

View File

@ -19,7 +19,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
deserializeJson(doc, "[\"example\",\"example\"]");
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
}
SECTION("Deduplicate keys") {
@ -42,7 +42,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
doc.add(std::string("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
}
SECTION("char*") {
@ -51,7 +51,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
doc.add(value);
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
}
SECTION("Arduino String") {
@ -59,7 +59,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
doc.add(String("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
}
SECTION("Flash string") {
@ -67,7 +67,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 0") {
doc.add(F("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 16);
CHECK(doc[0].as<char*>() != doc[1].as<char*>());
CHECK(doc[0].as<const char*>() != doc[1].as<const char*>());
}
}

View File

@ -19,7 +19,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
deserializeJson(doc, "[\"example\",\"example\"]");
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
}
SECTION("Deduplicate keys") {
@ -41,7 +41,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
doc.add(std::string("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
}
SECTION("char*") {
@ -50,7 +50,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
doc.add(value);
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
}
SECTION("Arduino String") {
@ -58,7 +58,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
doc.add(String("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
}
SECTION("Flash string") {
@ -66,7 +66,7 @@ TEST_CASE("ARDUINOJSON_ENABLE_STRING_DEDUPLICATION = 1") {
doc.add(F("example"));
CHECK(doc.memoryUsage() == JSON_ARRAY_SIZE(2) + 8);
CHECK(doc[0].as<char*>() == doc[1].as<char*>());
CHECK(doc[0].as<const char*>() == doc[1].as<const char*>());
}
}

View File

@ -65,7 +65,7 @@ class ElementProxy : public VariantOperators<ElementProxy<TArray> >,
}
template <typename T>
FORCE_INLINE typename VariantAs<T>::type as() const {
FORCE_INLINE T as() const {
return getUpstreamElement().template as<T>();
}

View File

@ -21,12 +21,12 @@ class JsonDocument : public Visitable {
}
template <typename T>
typename VariantAs<T>::type as() {
T as() {
return getVariant().template as<T>();
}
template <typename T>
typename VariantConstAs<T>::type as() const {
T as() const {
return getVariant().template as<T>();
}
@ -70,7 +70,7 @@ class JsonDocument : public Visitable {
}
bool set(const JsonDocument& src) {
return to<VariantRef>().set(src.as<VariantRef>());
return to<VariantRef>().set(src.as<VariantConstRef>());
}
template <typename T>

View File

@ -82,7 +82,7 @@ class MemberProxy : public VariantOperators<MemberProxy<TObject, TStringRef> >,
}
template <typename TValue>
FORCE_INLINE typename VariantAs<TValue>::type as() const {
FORCE_INLINE TValue as() const {
return getUpstreamMember().template as<TValue>();
}

View File

@ -16,42 +16,6 @@ class ObjectConstRef;
class VariantRef;
class VariantConstRef;
// A metafunction that returns the type of the value returned by
// VariantRef::as<T>()
template <typename T>
struct VariantAs {
typedef T type;
};
template <>
struct VariantAs<char*> {
typedef const char* type;
};
// A metafunction that returns the type of the value returned by
// VariantRef::as<T>()
template <typename T>
struct VariantConstAs {
typedef typename VariantAs<T>::type type;
};
template <>
struct VariantConstAs<VariantRef> {
typedef VariantConstRef type;
};
template <>
struct VariantConstAs<ObjectRef> {
typedef ObjectConstRef type;
};
template <>
struct VariantConstAs<ArrayRef> {
typedef ArrayConstRef type;
};
// ---
template <typename T>
inline typename enable_if<is_integral<T>::value && !is_same<bool, T>::value &&
!is_same<char, T>::value,
@ -80,10 +44,8 @@ inline typename enable_if<is_floating_point<T>::value, T>::type variantAs(
}
template <typename T>
inline typename enable_if<is_same<T, const char*>::value ||
is_same<T, char*>::value,
const char*>::type
variantAs(const VariantData* data) {
inline typename enable_if<is_same<T, const char*>::value, T>::type variantAs(
const VariantData* data) {
return data != 0 ? data->asString() : 0;
}

View File

@ -23,18 +23,25 @@ struct VariantOperators {
// int operator|(JsonVariant, int)
// float operator|(JsonVariant, float)
// bool operator|(JsonVariant, bool)
// const char* operator|(JsonVariant, const char*)
// char* operator|(JsonVariant, const char*)
template <typename T>
friend typename enable_if<!IsVariant<T>::value,
typename VariantAs<T>::type>::type
operator|(const TVariant &variant, T defaultValue) {
friend
typename enable_if<!IsVariant<T>::value && !is_array<T>::value, T>::type
operator|(const TVariant &variant, const T &defaultValue) {
if (variant.template is<T>())
return variant.template as<T>();
else
return defaultValue;
}
//
// const char* operator|(JsonVariant, const char*)
friend const char *operator|(const TVariant &variant,
const char *defaultValue) {
if (variant.template is<const char *>())
return variant.template as<const char *>();
else
return defaultValue;
}
//
// JsonVariant operator|(JsonVariant, JsonVariant)
template <typename T>
friend typename enable_if<IsVariant<T>::value, typename T::variant_type>::type

View File

@ -176,7 +176,7 @@ class VariantRef : public VariantRefBase<VariantData>,
#endif
template <typename T>
FORCE_INLINE typename VariantAs<T>::type as() const {
FORCE_INLINE T as() const {
/********************************************************************
** THIS IS NOT A BUG IN THE LIBRARY **
** -------------------------------- **
@ -187,11 +187,13 @@ class VariantRef : public VariantRefBase<VariantData>,
** For example: **
** char* name = doc["name"]; **
** char age = doc["age"]; **
** auto city = doc["city"].as<char*>() **
** Instead, use: **
** const char* name = doc["name"]; **
** int8_t age = doc["age"]; **
** auto city = doc["city"].as<const char*>() **
********************************************************************/
return variantAs<typename VariantAs<T>::type>(_data, _pool);
return variantAs<T>(_data, _pool);
}
template <typename T>
@ -291,8 +293,8 @@ class VariantConstRef : public VariantRefBase<const VariantData>,
}
template <typename T>
FORCE_INLINE typename VariantConstAs<T>::type as() const {
return variantAs<typename VariantConstAs<T>::type>(_data);
FORCE_INLINE T as() const {
return variantAs<T>(_data);
}
template <typename T>