Просмотр исходного кода

Added implicit conversion from `JsonArray` and `JsonObject` to `JsonVariant`

Benoit Blanchon 7 лет назад
Родитель
Сommit
29e71cbb16

+ 5 - 0
CHANGELOG.md

@@ -1,6 +1,11 @@
 ArduinoJson: change log
 =======================
 
+HEAD
+----
+
+* Added implicit conversion from `JsonArray` and `JsonObject` to `JsonVariant`
+
 v6.4.0-beta (2018-09-11)
 -----------
 

+ 17 - 143
src/ArduinoJson/Data/JsonVariantData.hpp

@@ -4,158 +4,32 @@
 
 #pragma once
 
-#include "../Numbers/parseFloat.hpp"
-#include "../Numbers/parseInteger.hpp"
 #include "JsonVariantContent.hpp"
 #include "JsonVariantType.hpp"
 
 namespace ArduinoJson {
 namespace Internals {
 
+// this struct must be a POD type to prevent error calling offsetof on clang
 struct JsonVariantData {
   JsonVariantType type;
   JsonVariantContent content;
-
-  JsonVariantData() {
-    type = JSON_NULL;
-  }
-
-  void setBoolean(bool value) {
-    type = JSON_BOOLEAN;
-    content.asInteger = static_cast<JsonUInt>(value);
-  }
-
-  void setFloat(JsonFloat value) {
-    type = JSON_FLOAT;
-    content.asFloat = value;
-  }
-
-  void setNegativeInteger(JsonUInt value) {
-    type = JSON_NEGATIVE_INTEGER;
-    content.asInteger = value;
-  }
-
-  void setPostiveInteger(JsonUInt value) {
-    type = JSON_POSITIVE_INTEGER;
-    content.asInteger = value;
-  }
-
-  void setOwnedString(const char *value) {
-    type = JSON_OWNED_STRING;
-    content.asString = value;
-  }
-
-  void setLinkedString(const char *value) {
-    type = JSON_LINKED_STRING;
-    content.asString = value;
-  }
-
-  void setOwnedRaw(const char *data, size_t size) {
-    type = JSON_OWNED_RAW;
-    content.asRaw.data = data;
-    content.asRaw.size = size;
-  }
-
-  void setLinkedRaw(const char *data, size_t size) {
-    type = JSON_LINKED_RAW;
-    content.asRaw.data = data;
-    content.asRaw.size = size;
-  }
-
-  void setNull() {
-    type = JSON_NULL;
-  }
-
-  JsonArrayData *toArray() {
-    type = JSON_ARRAY;
-    content.asArray.head = 0;
-    content.asArray.tail = 0;
-    return &content.asArray;
-  }
-
-  JsonObjectData *toObject() {
-    type = JSON_OBJECT;
-    content.asObject.head = 0;
-    content.asObject.tail = 0;
-    return &content.asObject;
-  }
-
-  JsonArrayData *asArray() {
-    return type == JSON_ARRAY ? &content.asArray : 0;
-  }
-
-  JsonObjectData *asObject() {
-    return type == JSON_OBJECT ? &content.asObject : 0;
-  }
-
-  template <typename T>
-  T asInteger() const {
-    switch (type) {
-      case JSON_POSITIVE_INTEGER:
-      case JSON_BOOLEAN:
-        return T(content.asInteger);
-      case JSON_NEGATIVE_INTEGER:
-        return T(~content.asInteger + 1);
-      case JSON_LINKED_STRING:
-      case JSON_OWNED_STRING:
-        return parseInteger<T>(content.asString);
-      case JSON_FLOAT:
-        return T(content.asFloat);
-      default:
-        return 0;
-    }
-  }
-
-  template <typename T>
-  T asFloat() const {
-    switch (type) {
-      case JSON_POSITIVE_INTEGER:
-      case JSON_BOOLEAN:
-        return static_cast<T>(content.asInteger);
-      case JSON_NEGATIVE_INTEGER:
-        return -static_cast<T>(content.asInteger);
-      case JSON_LINKED_STRING:
-      case JSON_OWNED_STRING:
-        return parseFloat<T>(content.asString);
-      case JSON_FLOAT:
-        return static_cast<T>(content.asFloat);
-      default:
-        return 0;
-    }
-  }
-
-  const char *asString() const {
-    return isString() ? content.asString : NULL;
-  }
-
-  bool isArray() const {
-    return type == JSON_ARRAY;
-  }
-
-  bool isBoolean() const {
-    return type == JSON_BOOLEAN;
-  }
-
-  bool isFloat() const {
-    return type == JSON_FLOAT || type == JSON_POSITIVE_INTEGER ||
-           type == JSON_NEGATIVE_INTEGER;
-  }
-
-  bool isInteger() const {
-    return type == JSON_POSITIVE_INTEGER || type == JSON_NEGATIVE_INTEGER;
-  }
-
-  bool isNull() const {
-    return type == JSON_NULL;
-  }
-
-  bool isObject() const {
-    return type == JSON_OBJECT;
-  }
-
-  bool isString() const {
-    return type == JSON_LINKED_STRING || type == JSON_OWNED_STRING;
-  }
 };
+
+inline JsonVariantData *getVariantData(JsonArrayData *arr) {
+  const ptrdiff_t offset = offsetof(JsonVariantData, content) -
+                           offsetof(JsonVariantContent, asArray);
+  if (!arr) return 0;
+  return reinterpret_cast<JsonVariantData *>(reinterpret_cast<char *>(arr) -
+                                             offset);
+}
+
+inline JsonVariantData *getVariantData(JsonObjectData *obj) {
+  const ptrdiff_t offset = offsetof(JsonVariantData, content) -
+                           offsetof(JsonVariantContent, asObject);
+  if (!obj) return 0;
+  return reinterpret_cast<JsonVariantData *>(reinterpret_cast<char *>(obj) -
+                                             offset);
+}
 }  // namespace Internals
 }  // namespace ArduinoJson

+ 1 - 1
src/ArduinoJson/DynamicJsonDocument.hpp

@@ -37,7 +37,7 @@ class DynamicJsonDocument {
 
   void clear() {
     _memoryPool.clear();
-    _rootData.setNull();
+    _rootData.type = Internals::JSON_NULL;
   }
 
   size_t memoryUsage() const {

+ 7 - 2
src/ArduinoJson/JsonArray.hpp

@@ -26,9 +26,14 @@ class JsonArray {
   typedef JsonArrayIterator iterator;
 
   FORCE_INLINE JsonArray() : _memoryPool(0), _data(0) {}
-  FORCE_INLINE JsonArray(Internals::MemoryPool* buf,
+  FORCE_INLINE JsonArray(Internals::MemoryPool* pool,
                          Internals::JsonArrayData* arr)
-      : _memoryPool(buf), _data(arr) {}
+      : _memoryPool(pool), _data(arr) {}
+
+  operator JsonVariant() {
+    using namespace Internals;
+    return JsonVariant(_memoryPool, getVariantData(_data));
+  }
 
   // Adds the specified value at the end of the array.
   //

+ 5 - 0
src/ArduinoJson/JsonObject.hpp

@@ -24,6 +24,11 @@ class JsonObject {
                           Internals::JsonObjectData* object)
       : _memoryPool(buf), _data(object) {}
 
+  operator JsonVariant() {
+    using namespace Internals;
+    return JsonVariant(_memoryPool, getVariantData(_data));
+  }
+
   FORCE_INLINE iterator begin() const {
     if (!_data) return iterator();
     return iterator(_memoryPool, _data->head);

+ 96 - 31
src/ArduinoJson/JsonVariant.hpp

@@ -11,6 +11,8 @@
 #include "JsonVariant.hpp"
 #include "JsonVariantBase.hpp"
 #include "Memory/MemoryPool.hpp"
+#include "Numbers/parseFloat.hpp"
+#include "Numbers/parseInteger.hpp"
 #include "Polyfills/type_traits.hpp"
 #include "Serialization/DynamicStringWriter.hpp"
 #include "SerializedValue.hpp"
@@ -41,7 +43,8 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
   // set(bool value)
   FORCE_INLINE bool set(bool value) {
     if (!_data) return false;
-    _data->setBoolean(value);
+    _data->type = Internals::JSON_BOOLEAN;
+    _data->content.asInteger = static_cast<Internals::JsonUInt>(value);
     return true;
   }
 
@@ -52,7 +55,8 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
       T value, typename Internals::enable_if<
                    Internals::is_floating_point<T>::value>::type * = 0) {
     if (!_data) return false;
-    _data->setFloat(static_cast<Internals::JsonFloat>(value));
+    _data->type = Internals::JSON_FLOAT;
+    _data->content.asFloat = static_cast<Internals::JsonFloat>(value);
     return true;
   }
 
@@ -68,10 +72,13 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
                                     Internals::is_signed<T>::value>::type * =
           0) {
     if (!_data) return false;
-    if (value >= 0)
-      _data->setPostiveInteger(static_cast<Internals::JsonUInt>(value));
-    else
-      _data->setNegativeInteger(~static_cast<Internals::JsonUInt>(value) + 1);
+    if (value >= 0) {
+      _data->type = Internals::JSON_POSITIVE_INTEGER;
+      _data->content.asInteger = static_cast<Internals::JsonUInt>(value);
+    } else {
+      _data->type = Internals::JSON_NEGATIVE_INTEGER;
+      _data->content.asInteger = ~static_cast<Internals::JsonUInt>(value) + 1;
+    }
     return true;
   }
 
@@ -85,14 +92,17 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
                                     Internals::is_unsigned<T>::value>::type * =
           0) {
     if (!_data) return false;
-    _data->setPostiveInteger(static_cast<Internals::JsonUInt>(value));
+    _data->type = Internals::JSON_POSITIVE_INTEGER;
+    _data->content.asInteger = static_cast<Internals::JsonUInt>(value);
     return true;
   }
 
   // set(SerializedValue<const char *>)
   FORCE_INLINE bool set(Internals::SerializedValue<const char *> value) {
     if (!_data) return false;
-    _data->setLinkedRaw(value.data(), value.size());
+    _data->type = Internals::JSON_LINKED_RAW;
+    _data->content.asRaw.data = value.data();
+    _data->content.asRaw.size = value.size();
     return true;
   }
 
@@ -107,11 +117,15 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
     if (!_data) return false;
     const char *dup =
         Internals::makeString(value.data(), value.size()).save(_memoryPool);
-    if (dup)
-      _data->setOwnedRaw(dup, value.size());
-    else
-      _data->setNull();
-    return true;
+    if (dup) {
+      _data->type = Internals::JSON_OWNED_RAW;
+      _data->content.asRaw.data = dup;
+      _data->content.asRaw.size = value.size();
+      return true;
+    } else {
+      _data->type = Internals::JSON_NULL;
+      return false;
+    }
   }
 
   // set(const std::string&)
@@ -124,10 +138,11 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
     if (!_data) return false;
     const char *dup = Internals::makeString(value).save(_memoryPool);
     if (dup) {
-      _data->setOwnedString(dup);
+      _data->type = Internals::JSON_OWNED_STRING;
+      _data->content.asString = dup;
       return true;
     } else {
-      _data->setNull();
+      _data->type = Internals::JSON_NULL;
       return false;
     }
   }
@@ -141,10 +156,11 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
     if (!_data) return false;
     const char *dup = Internals::makeString(value).save(_memoryPool);
     if (dup) {
-      _data->setOwnedString(dup);
+      _data->type = Internals::JSON_OWNED_STRING;
+      _data->content.asString = dup;
       return true;
     } else {
-      _data->setNull();
+      _data->type = Internals::JSON_NULL;
       return false;
     }
   }
@@ -152,7 +168,8 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
   // set(const char*);
   FORCE_INLINE bool set(const char *value) {
     if (!_data) return false;
-    _data->setLinkedString(value);
+    _data->type = Internals::JSON_LINKED_STRING;
+    _data->content.asString = value;
     return true;
   }
 
@@ -179,14 +196,28 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
   FORCE_INLINE const typename Internals::enable_if<
       Internals::is_integral<T>::value, T>::type
   as() const {
-    return _data ? _data->asInteger<T>() : T();
+    if (!_data) return 0;
+    switch (_data->type) {
+      case Internals::JSON_POSITIVE_INTEGER:
+      case Internals::JSON_BOOLEAN:
+        return T(_data->content.asInteger);
+      case Internals::JSON_NEGATIVE_INTEGER:
+        return T(~_data->content.asInteger + 1);
+      case Internals::JSON_LINKED_STRING:
+      case Internals::JSON_OWNED_STRING:
+        return Internals::parseInteger<T>(_data->content.asString);
+      case Internals::JSON_FLOAT:
+        return T(_data->content.asFloat);
+      default:
+        return 0;
+    }
   }
   // bool as<bool>() const
   template <typename T>
   FORCE_INLINE const typename Internals::enable_if<
       Internals::is_same<T, bool>::value, T>::type
   as() const {
-    return _data && _data->asInteger<int>() != 0;
+    return as<int>() != 0;
   }
   //
   // double as<double>() const;
@@ -195,7 +226,21 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
   FORCE_INLINE const typename Internals::enable_if<
       Internals::is_floating_point<T>::value, T>::type
   as() const {
-    return _data ? _data->asFloat<T>() : 0;
+    if (!_data) return 0;
+    switch (_data->type) {
+      case Internals::JSON_POSITIVE_INTEGER:
+      case Internals::JSON_BOOLEAN:
+        return static_cast<T>(_data->content.asInteger);
+      case Internals::JSON_NEGATIVE_INTEGER:
+        return -static_cast<T>(_data->content.asInteger);
+      case Internals::JSON_LINKED_STRING:
+      case Internals::JSON_OWNED_STRING:
+        return Internals::parseFloat<T>(_data->content.asString);
+      case Internals::JSON_FLOAT:
+        return static_cast<T>(_data->content.asFloat);
+      default:
+        return 0;
+    }
   }
   //
   // const char* as<const char*>() const;
@@ -206,7 +251,12 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
           Internals::is_same<T, char *>::value,
       const char *>::type
   as() const {
-    return _data ? _data->asString() : 0;
+    if (!_data) return 0;
+    if (_data && (_data->type == Internals::JSON_LINKED_STRING ||
+                  _data->type == Internals::JSON_OWNED_STRING))
+      return _data->content.asString;
+    else
+      return 0;
   }
   //
   // std::string as<std::string>() const;
@@ -216,7 +266,7 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
       typename Internals::enable_if<Internals::IsWriteableString<T>::value,
                                     T>::type
       as() const {
-    const char *cstr = _data ? _data->asString() : 0;
+    const char *cstr = as<const char *>();
     if (cstr) return T(cstr);
     T s;
     serializeJson(*this, s);
@@ -266,7 +316,8 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
   FORCE_INLINE typename Internals::enable_if<Internals::is_integral<T>::value,
                                              bool>::type
   is() const {
-    return _data && _data->isInteger();
+    return _data && (_data->type == Internals::JSON_POSITIVE_INTEGER ||
+                     _data->type == Internals::JSON_NEGATIVE_INTEGER);
   }
   //
   // bool is<double>() const;
@@ -276,7 +327,9 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
       typename Internals::enable_if<Internals::is_floating_point<T>::value,
                                     bool>::type
       is() const {
-    return _data && _data->isFloat();
+    return _data && (_data->type == Internals::JSON_FLOAT ||
+                     _data->type == Internals::JSON_POSITIVE_INTEGER ||
+                     _data->type == Internals::JSON_NEGATIVE_INTEGER);
   }
   //
   // bool is<bool>() const
@@ -284,7 +337,7 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
   FORCE_INLINE typename Internals::enable_if<Internals::is_same<T, bool>::value,
                                              bool>::type
   is() const {
-    return _data && _data->isBoolean();
+    return _data && _data->type == Internals::JSON_BOOLEAN;
   }
   //
   // bool is<const char*>() const;
@@ -295,7 +348,8 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
           Internals::is_same<T, char *>::value,
       bool>::type
   is() const {
-    return _data && _data->isString();
+    return _data && (_data->type == Internals::JSON_LINKED_STRING ||
+                     _data->type == Internals::JSON_OWNED_STRING);
   }
   //
   // bool is<JsonArray> const;
@@ -306,7 +360,7 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
                          JsonArray>::value,
       bool>::type
   is() const {
-    return _data && _data->isArray();
+    return _data && _data->type == Internals::JSON_ARRAY;
   }
   //
   // bool is<JsonObject> const;
@@ -317,11 +371,11 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
                          JsonObject>::value,
       bool>::type
   is() const {
-    return _data && _data->isObject();
+    return _data && _data->type == Internals::JSON_OBJECT;
   }
 
   FORCE_INLINE bool isNull() const {
-    return _data == 0 || _data->isNull();
+    return _data == 0 || _data->type == Internals::JSON_NULL;
   }
 
   FORCE_INLINE bool isInvalid() const {
@@ -354,5 +408,16 @@ class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
  private:
   Internals::MemoryPool *_memoryPool;
   Internals::JsonVariantData *_data;
-};  // namespace ArduinoJson
+};
+
+class JsonVariantLocal : public JsonVariant {
+ public:
+  explicit JsonVariantLocal(Internals::MemoryPool *memoryPool)
+      : JsonVariant(memoryPool, &_localData) {
+    _localData.type = Internals::JSON_NULL;
+  }
+
+ private:
+  Internals::JsonVariantData _localData;
+};
 }  // namespace ArduinoJson

+ 18 - 6
src/ArduinoJson/JsonVariantImpl.hpp

@@ -34,7 +34,7 @@ inline bool JsonVariant::set(
 inline bool JsonVariant::set(const JsonVariant& value) {
   if (!_data) return false;
   if (!value._data) {
-    _data->setNull();
+    _data->type = Internals::JSON_NULL;
     return true;
   }
   switch (value._data->type) {
@@ -59,7 +59,10 @@ inline typename Internals::enable_if<
                        JsonArray>::value,
     JsonArray>::type
 JsonVariant::as() const {
-  return _data ? JsonArray(_memoryPool, _data->asArray()) : JsonArray();
+  if (_data && _data->type == Internals::JSON_ARRAY)
+    return JsonArray(_memoryPool, &_data->content.asArray);
+  else
+    return JsonArray();
 }
 
 template <typename T>
@@ -68,7 +71,10 @@ inline typename Internals::enable_if<
                        JsonObject>::value,
     T>::type
 JsonVariant::as() const {
-  return _data ? JsonObject(_memoryPool, _data->asObject()) : JsonObject();
+  if (_data && _data->type == Internals::JSON_OBJECT)
+    return JsonObject(_memoryPool, &_data->content.asObject);
+  else
+    return JsonObject();
 }
 
 template <typename T>
@@ -76,7 +82,10 @@ inline typename Internals::enable_if<Internals::is_same<T, JsonArray>::value,
                                      JsonArray>::type
 JsonVariant::to() {
   if (!_data) return JsonArray();
-  return JsonArray(_memoryPool, _data->toArray());
+  _data->type = Internals::JSON_ARRAY;
+  _data->content.asArray.head = 0;
+  _data->content.asArray.tail = 0;
+  return JsonArray(_memoryPool, &_data->content.asArray);
 }
 
 template <typename T>
@@ -84,7 +93,10 @@ typename Internals::enable_if<Internals::is_same<T, JsonObject>::value,
                               JsonObject>::type
 JsonVariant::to() {
   if (!_data) return JsonObject();
-  return JsonObject(_memoryPool, _data->toObject());
+  _data->type = Internals::JSON_OBJECT;
+  _data->content.asObject.head = 0;
+  _data->content.asObject.tail = 0;
+  return JsonObject(_memoryPool, &_data->content.asObject);
 }
 
 template <typename T>
@@ -92,7 +104,7 @@ typename Internals::enable_if<Internals::is_same<T, JsonVariant>::value,
                               JsonVariant>::type
 JsonVariant::to() {
   if (!_data) return JsonVariant();
-  _data->setNull();
+  _data->type = Internals::JSON_NULL;
   return *this;
 }
 

+ 3 - 4
src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp

@@ -277,13 +277,12 @@ class MsgPackDeserializer {
     if (_nestingLimit == 0) return DeserializationError::TooDeep;
     --_nestingLimit;
     for (; n; --n) {
-      JsonVariantData keyData;
-      JsonVariant key(_memoryPool, &keyData);
+      JsonVariantLocal key(_memoryPool);
       DeserializationError err = parse(key);
       if (err) return err;
-      if (!keyData.isString()) return DeserializationError::NotSupported;
+      if (!key.is<char *>()) return DeserializationError::NotSupported;
 
-      JsonVariant value = object.set(keyData.asString());
+      JsonVariant value = object.set(key.as<char *>());
       if (value.isInvalid()) return DeserializationError::NoMemory;
 
       err = parse(value);

+ 1 - 1
src/ArduinoJson/StaticJsonDocument.hpp

@@ -39,7 +39,7 @@ class StaticJsonDocument {
 
   void clear() {
     _memoryPool.clear();
-    _rootData.setNull();
+    _rootData.type = Internals::JSON_NULL;
   }
 
   size_t memoryUsage() const {

+ 1 - 0
test/JsonVariant/CMakeLists.txt

@@ -8,6 +8,7 @@ add_executable(JsonVariantTests
 	copy.cpp
 	is.cpp
 	isnull.cpp
+	misc.cpp
 	or.cpp
 	set_get.cpp
 	subscript.cpp

+ 50 - 0
test/JsonVariant/misc.cpp

@@ -0,0 +1,50 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#include <ArduinoJson.h>
+#include <catch.hpp>
+
+TEST_CASE("JsonVariant from JsonArray") {
+  SECTION("JsonArray is null") {
+    JsonArray arr;
+    JsonVariant v = arr;
+    REQUIRE(v.isNull() == true);
+  }
+
+  SECTION("JsonArray is not null") {
+    DynamicJsonDocument doc;
+    JsonArray arr = doc.to<JsonArray>();
+    arr.add(12);
+    arr.add(34);
+
+    JsonVariant v = arr;
+
+    REQUIRE(v.is<JsonArray>() == true);
+    REQUIRE(v.size() == 2);
+    REQUIRE(v[0] == 12);
+    REQUIRE(v[1] == 34);
+  }
+}
+
+TEST_CASE("JsonVariant from JsonObject") {
+  SECTION("JsonObject is null") {
+    JsonObject obj;
+    JsonVariant v = obj;
+    REQUIRE(v.isNull() == true);
+  }
+
+  SECTION("JsonObject is not null") {
+    DynamicJsonDocument doc;
+    JsonObject obj = doc.to<JsonObject>();
+    obj["a"] = 12;
+    obj["b"] = 34;
+
+    JsonVariant v = obj;
+
+    REQUIRE(v.is<JsonObject>() == true);
+    REQUIRE(v.size() == 2);
+    REQUIRE(v["a"] == 12);
+    REQUIRE(v["b"] == 34);
+  }
+}

+ 15 - 2
test/JsonVariant/set_get.cpp

@@ -205,7 +205,20 @@ TEST_CASE("JsonVariant and strings") {
 
     REQUIRE(variant == "hello");
   }
+}
+
+TEST_CASE("JsonVariant with not enough memory") {
+  StaticJsonDocument<1> doc;
 
-  // TODO: string
-  // TODO: serialized()
+  JsonVariant v = doc.to<JsonVariant>();
+
+  SECTION("std::string") {
+    v.set(std::string("hello"));
+    REQUIRE(v.isNull());
+  }
+
+  SECTION("Serialized<std::string>") {
+    v.set(serialized(std::string("hello")));
+    REQUIRE(v.isNull());
+  }
 }