diff --git a/.gitignore b/.gitignore
index 5ed97f792ec8e3f95437d94e572a2d16c0b39725..7ddf9b7e3c77a0cc492c738c47b5a97a6b7e7997 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,3 +145,12 @@ flycheck_*.el
 
 # Python/Sphinx
 env/
+
+# Conan build files
+build_tools
+conan.lock
+conan_imports_manifest.txt
+conanbuildinfo.cmake
+conanbuildinfo.txt
+conaninfo.txt
+graph_info.json
diff --git a/Makefile b/Makefile
index a024d31675b8ca1e3c1ec9967b0e5e3045872f25..367c0de989e9fa373b7a01afffc2720a5834508d 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,23 @@
 
 
 CLANG_FORMAT ?= clang-format-11
+CONAN_SETTINGS = "compiler.libcxx=libstdc++11"
+
+# OS specific handling, with code by Ken Jackson and oHo,
+# from https://stackoverflow.com/a/52062069/232888 and
+# https://stackoverflow.com/a/14777895/232888
+ifeq '$(findstring ;,$(PATH))' ';'
+	DETECTED_OS := Windows
+else
+	DETECTED_OS := $(shell uname 2>/dev/null || echo Unknown)
+	DETECTED_OS := $(patsubst CYGWIN%,Cygwin,$(DETECTED_OS))
+	DETECTED_OS := $(patsubst MSYS%,MSYS,$(DETECTED_OS))
+	DETECTED_OS := $(patsubst MINGW%,MSYS,$(DETECTED_OS))
+endif
+ifeq ($(DETECTED_OS),Darwin)  # Test if we are on MacOS
+	CONAN_SETTINGS := "cppstd=11"
+endif
+
 
 .PHONY: help
 help:
@@ -36,11 +53,13 @@ style:
 .PHONY: style
 
 conan-install:
-	conan install . -s "compiler.libcxx=libstdc++11"
+	conan install .  -s $(CONAN_SETTINGS) || \
+	  (echo "'conan install' failed, trying to build from sources..."; \
+	   conan install . -s $(CONAN_SETTINGS) --build=missing)
 .PHONY: conan-install
 
 conan-create:
-	conan create . -s "compiler.libcxx=libstdc++11"
+	conan create . -s $(CONAN_SETTINGS)
 .PHONY: conan-create
 
 conan: conan-install conan-create
diff --git a/doc/README_SETUP.md b/doc/README_SETUP.md
index a5976af95de78a01e6ae174e39cc6a66881c28ac..46026eee1da7ea078f93687812833f957bd75312 100644
--- a/doc/README_SETUP.md
+++ b/doc/README_SETUP.md
@@ -34,6 +34,11 @@ have to add `build/lib/` (or, alternatively after installation,
 `CMAKE_INSTALL_PREFIX/lib`) to your `DYLD_LIBRARY_PATH` environmental
 variable.
 
+#### Problems and solutions ####
+
+- Make sure that your conan version supports your XCode version.  A typical symptom of version
+  mismatch is for example conan complaining about incompatible `compiler.version` settings.
+
 ### How to build on Windows
 
 We use [Visual Studio 2019](https://visualstudio.microsoft.com/de/vs/features/cplusplus/)
diff --git a/include/caosdb/data_type.h b/include/caosdb/data_type.h
index e6b92d53d6ddb1aa8360af55f6438d23eafff7ae..42d376698872e086c05a5905484e2f63c11dd346 100644
--- a/include/caosdb/data_type.h
+++ b/include/caosdb/data_type.h
@@ -40,6 +40,7 @@ using ProtoReferenceDataType = caosdb::entity::v1alpha1::ReferenceDataType;
 using DataTypeCase = caosdb::entity::v1alpha1::DataType::DataTypeCase;
 using ListDataTypeCase = caosdb::entity::v1alpha1::ListDataType::ListDataTypeCase;
 using caosdb::utility::ProtoMessageWrapper;
+using caosdb::utility::ScalarProtoMessageWrapper;
 
 class Entity;
 class Property;
@@ -68,7 +69,7 @@ const std::map<AtomicDataType, std::string> atomicdatatype_names = {
 class DataType;
 class ListDataType;
 
-class ReferenceDataType : public ProtoMessageWrapper<ProtoDataType> {
+class ReferenceDataType : public ScalarProtoMessageWrapper<ProtoDataType> {
 public:
   [[nodiscard]] inline auto GetName() const noexcept -> const std::string & {
     // is list of reference?
@@ -91,11 +92,11 @@ protected:
   inline static auto Create(ProtoDataType *wrapped) -> std::unique_ptr<ReferenceDataType> {
     return std::unique_ptr<ReferenceDataType>(new ReferenceDataType(wrapped));
   }
-  ReferenceDataType() : ProtoMessageWrapper<ProtoDataType>() {}
-  ReferenceDataType(ProtoDataType *wrapped) : ProtoMessageWrapper<ProtoDataType>(wrapped) {}
+  ReferenceDataType() : ScalarProtoMessageWrapper<ProtoDataType>() {}
+  ReferenceDataType(ProtoDataType *wrapped) : ScalarProtoMessageWrapper<ProtoDataType>(wrapped) {}
 };
 
-class ListDataType : public ProtoMessageWrapper<ProtoDataType> {
+class ListDataType : public ScalarProtoMessageWrapper<ProtoDataType> {
 public:
   [[nodiscard]] inline auto IsListOfReference() const noexcept -> bool {
     return this->wrapped->list_data_type().list_data_type_case() ==
@@ -130,21 +131,22 @@ protected:
   inline static auto Create(ProtoDataType *wrapped) -> std::unique_ptr<ListDataType> {
     return std::unique_ptr<ListDataType>(new ListDataType(wrapped));
   }
-  ListDataType() : ProtoMessageWrapper<ProtoDataType>() {}
+  ListDataType() : ScalarProtoMessageWrapper<ProtoDataType>() {}
 
-  ListDataType(ProtoDataType *wrapped) : ProtoMessageWrapper<ProtoDataType>(wrapped) {}
+  ListDataType(ProtoDataType *wrapped) : ScalarProtoMessageWrapper<ProtoDataType>(wrapped) {}
 
   mutable std::unique_ptr<ReferenceDataType> reference_data_type;
 };
 
-class DataType : public ProtoMessageWrapper<ProtoDataType> {
+class DataType : public ScalarProtoMessageWrapper<ProtoDataType> {
 public:
-  DataType(ProtoDataType *wrapped) : ProtoMessageWrapper<ProtoDataType>(wrapped) {}
-  DataType() : ProtoMessageWrapper<ProtoDataType>() {}
+  DataType(ProtoDataType *wrapped) : ScalarProtoMessageWrapper<ProtoDataType>(wrapped) {}
+  DataType() : ScalarProtoMessageWrapper<ProtoDataType>(static_cast<ProtoDataType *>(nullptr)) {}
   /**
    * Create an AtomicDataType typed DataType.  For references, use the std::string constructor.
    */
-  DataType(AtomicDataType data_type, bool list_type = false) : DataType() {
+  DataType(AtomicDataType data_type, bool list_type = false)
+    : ScalarProtoMessageWrapper<ProtoDataType>() {
     if (list_type) {
       this->wrapped->mutable_list_data_type()->set_atomic_data_type(
         static_cast<ProtoAtomicDataType>(data_type));
@@ -155,7 +157,8 @@ public:
   /**
    * Create a reference typed DataType.
    */
-  DataType(const std::string &data_type, bool list_type = false) : DataType() {
+  DataType(const std::string &data_type, bool list_type = false)
+    : ScalarProtoMessageWrapper<ProtoDataType>() {
     if (list_type) {
       this->wrapped->mutable_list_data_type()->mutable_reference_data_type()->set_name(data_type);
     } else {
@@ -164,27 +167,28 @@ public:
   }
 
   inline static auto ListOf(const AtomicDataType &atomic_data_type) -> DataType {
-    DataType result;
-    result.wrapped->mutable_list_data_type()->set_atomic_data_type(
-      static_cast<ProtoAtomicDataType>(atomic_data_type));
-    return result;
+    return DataType(atomic_data_type, true);
   }
   inline static auto ListOf(const std::string reference_data_type) -> DataType {
-    DataType result;
-    result.wrapped->mutable_list_data_type()->mutable_reference_data_type()->set_name(
-      reference_data_type);
-    return result;
+    return DataType(reference_data_type, true);
   }
 
+  [[nodiscard]] inline auto IsUndefined() const noexcept -> bool {
+    return this->wrapped == nullptr;
+  }
   [[nodiscard]] inline auto IsAtomic() const noexcept -> bool {
-    return this->wrapped->data_type_case() == DataTypeCase::kAtomicDataType;
+    return !IsUndefined() && this->wrapped->data_type_case() == DataTypeCase::kAtomicDataType;
   }
   [[nodiscard]] inline auto GetAsAtomic() const noexcept -> AtomicDataType {
+    if (!IsAtomic()) {
+      static AtomicDataType undefined;
+      return undefined;
+    }
     return static_cast<AtomicDataType>(this->wrapped->atomic_data_type());
   }
 
   [[nodiscard]] inline auto IsReference() const noexcept -> bool {
-    return this->wrapped->data_type_case() == DataTypeCase::kReferenceDataType;
+    return !IsUndefined() && this->wrapped->data_type_case() == DataTypeCase::kReferenceDataType;
   }
   [[nodiscard]] inline auto GetAsReference() const noexcept -> const ReferenceDataType & {
     if (!IsReference()) {
@@ -197,7 +201,7 @@ public:
   }
 
   [[nodiscard]] inline auto IsList() const noexcept -> bool {
-    return this->wrapped->data_type_case() == DataTypeCase::kListDataType;
+    return !IsUndefined() && this->wrapped->data_type_case() == DataTypeCase::kListDataType;
   }
 
   [[nodiscard]] inline auto GetAsList() const noexcept -> const ListDataType & {
@@ -209,9 +213,18 @@ public:
     return *list_data_type;
   }
 
+  /**
+   * Return true if `other` is equal to this object.
+   *
+   * This compares the underlying wrapped ProtoMessages and return true if they
+   * are both nullptrs or if the serialization is equal.
+   */
   inline auto operator==(const DataType &other) const noexcept -> bool {
-    // TODO(tf) Is this safe?
-    return this->wrapped->SerializeAsString() == other.wrapped->SerializeAsString();
+    if (this->wrapped != nullptr && other.wrapped != nullptr) {
+      return this->wrapped->SerializeAsString() == other.wrapped->SerializeAsString();
+    }
+    // both nullptr?
+    return this->wrapped == other.wrapped;
   }
 
   friend class Entity;
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index de6d4928771af83edca68d748d73b58f690a35f7..6de939c37f19d9419815dcb8b7001fd19050133f 100644
--- a/include/caosdb/entity.h
+++ b/include/caosdb/entity.h
@@ -45,7 +45,6 @@
 #include <boost/preprocessor/seq/limits/size_256.hpp>  // for BOOST_PP_SEQ_...
 #include <cstdint>                                     // for int64_t
 #include <google/protobuf/message.h>                   // for RepeatedPtrField
-#include <google/protobuf/util/json_util.h>            // for MessageToJson...
 #include <iosfwd>                                      // for streamsize
 #include <iterator>                                    // for iterator, output_iterato...
 #include <map>                                         // for map
@@ -53,6 +52,7 @@
 #include <stdexcept>                                   // for out_of_range
 #include <string>                                      // for string, basic...
 #include <vector>                                      // for vector
+#include <utility>                                     // for move
 
 namespace caosdb::entity {
 using boost::filesystem::exists;
@@ -63,12 +63,15 @@ using ProtoProperty = caosdb::entity::v1alpha1::Property;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using ProtoFileDescriptor = caosdb::entity::v1alpha1::FileDescriptor;
 using ProtoMessage = caosdb::entity::v1alpha1::Message;
+using ProtoValue = caosdb::entity::v1alpha1::Value;
+using ProtoDataType = caosdb::entity::v1alpha1::DataType;
 using caosdb::entity::v1alpha1::EntityRole;
 using ProtoImportance = caosdb::entity::v1alpha1::Importance;
 using caosdb::StatusCode;
 using caosdb::entity::v1alpha1::EntityResponse;
 using caosdb::entity::v1alpha1::FileTransmissionId;
 using caosdb::utility::get_arena;
+using caosdb::utility::ProtoMessageWrapper;
 using ::google::protobuf::RepeatedPtrField;
 using google::protobuf::RepeatedPtrField;
 
@@ -117,15 +120,18 @@ struct FileDescriptor {
  * Abstract base class for Messages, Properties and Parents container classes.
  *
  * This is a list-like class.
+ *
+ * This class wraps a RepeatedPtrField.
  */
-template <typename T, typename P> class RepeatedPtrFieldWrapper {
+template <typename T, typename P>
+class RepeatedPtrFieldWrapper : public ProtoMessageWrapper<RepeatedPtrField<P>> {
   class iterator;
 
 public:
   /**
    * Return the current size of the container.
    */
-  [[nodiscard]] inline auto size() const -> int { return wrapped->size(); }
+  [[nodiscard]] inline auto size() const -> int { return this->wrapped->size(); }
   /**
    * Return a const reference to the element at the given index.
    */
@@ -139,10 +145,11 @@ public:
       throw std::out_of_range("Container has size " + std::to_string(size()));
     }
     if (cache.count(index) == 0) {
-      cache.emplace(index, T(&(wrapped->at(index))));
+      cache.emplace(index, T(this->wrapped->Mutable(index)));
     }
     return &(cache.at(index));
   }
+
   /**
    * Return iterator positioned at the beginning of the list.
    */
@@ -164,10 +171,26 @@ public:
 
   virtual ~RepeatedPtrFieldWrapper(){};
 
+  inline auto ToString() const noexcept -> const std::string override {
+    if (this->size() == 0) {
+      return "[]\n";
+    }
+    std::string result("[\n");
+    for (int i = 0; i < this->size();) {
+      CAOSDB_DEBUG_MESSAGE_STRING(this->wrapped->at(i), next);
+      result += next;
+      if (++i < this->size()) {
+        result.replace(result.size() - 1, 1, ",\n");
+      }
+    }
+
+    return result.append(std::string("]\n"));
+  }
+
 protected:
-  RepeatedPtrFieldWrapper(){};
-  explicit inline RepeatedPtrFieldWrapper(::google::protobuf::RepeatedPtrField<P> *wrapped)
-    : wrapped(wrapped){};
+  RepeatedPtrFieldWrapper() : ProtoMessageWrapper<RepeatedPtrField<P>>(){};
+  explicit inline RepeatedPtrFieldWrapper(RepeatedPtrField<P> *wrapped)
+    : ProtoMessageWrapper<RepeatedPtrField<P>>(wrapped) {}
 
   /**
    * Append an element. This adds the element to the end of the wrapped list
@@ -175,13 +198,7 @@ protected:
    */
   inline auto Append(const T &element) -> void {
     auto *destination = this->wrapped->Add();
-    destination->Swap(element.wrapped);
-
-    // Clear the originally wrapped object and return it to the Arena
-    element.wrapped->Clear();
-
-    // set the pointer to the new object which is owned by the RepeatedPtrField
-    element.wrapped = destination;
+    destination->CopyFrom(*element.wrapped);
   }
 
   /**
@@ -202,11 +219,12 @@ protected:
         cache.insert(std::move(handle));
       }
     }
+    // this is possible because cache is an ordered map
+    cache.erase(cache.find(size()), cache.end());
   }
 
   inline auto Clear() noexcept -> void { this->wrapped->Clear(); }
 
-  ::google::protobuf::RepeatedPtrField<P> *wrapped;
   mutable std::map<int, T> cache;
 
 private:
@@ -282,7 +300,7 @@ auto RepeatedPtrFieldWrapper<T, P>::end() const -> const RepeatedPtrFieldWrapper
  * A Message object can be thought of as kinf of a generalized error object in
  * other frameworks. Please have a look at MessageCodes for more details.
  */
-class Message {
+class Message : public ScalarProtoMessageWrapper<ProtoMessage> {
 public:
   /**
    * Get the code of this message.
@@ -310,9 +328,8 @@ public:
   friend class RepeatedPtrFieldWrapper<Message, ProtoMessage>;
 
 private:
-  explicit inline Message(ProtoMessage *wrapped) : wrapped(wrapped){};
-
-  ProtoMessage *wrapped;
+  explicit inline Message(ProtoMessage *wrapped)
+    : ScalarProtoMessageWrapper<ProtoMessage>(wrapped){};
 };
 
 /**
@@ -338,10 +355,10 @@ private:
  * words, this class wraps a protobuf message and provides getter and setter
  * methods.
  */
-class Parent {
+class Parent : public ScalarProtoMessageWrapper<ProtoParent> {
 public:
-  explicit inline Parent(ProtoParent *wrapped) : wrapped(wrapped){};
-  Parent();
+  explicit inline Parent(ProtoParent *wrapped) : ScalarProtoMessageWrapper<ProtoParent>(wrapped){};
+  Parent() : ScalarProtoMessageWrapper<ProtoParent>(){};
 
   /**
    * Return the id of the parent entity.
@@ -365,19 +382,6 @@ public:
    */
   auto SetName(const std::string &name) -> void;
 
-  /**
-   * Return a json string representing this parent.
-   *
-   * This is intended for debugging.
-   */
-  inline auto ToString() const -> const std::string {
-    google::protobuf::util::JsonPrintOptions options;
-    options.add_whitespace = true;
-    std::string out;
-    google::protobuf::util::MessageToJsonString(*(this->wrapped), &out, options);
-    return out;
-  }
-
   // TODO(fspreck) Finish the following implementations once we have
   // decided how to attach messages to parents.
   // /**
@@ -410,22 +414,6 @@ public:
   friend class RepeatedPtrFieldWrapper<Parent, ProtoParent>;
 
 private:
-  /**
-   * Return an empty protobuf message pointer.
-   *
-   * This function is called by the default constructor of the
-   * caosdb::entity::Parent class and the protobuf message is used as the
-   * storage-backend for the new Parent instance.
-   *
-   * An 'Arena' takes care of the the memory management. Don't try to delete
-   * this.
-   */
-  static auto CreateProtoParent() -> ProtoParent *;
-
-  /**
-   * Message which serves as storage backend.
-   */
-  mutable ProtoParent *wrapped;
   // Messages errors;
   // Messages warnings;
   // Messages infos;
@@ -454,12 +442,40 @@ private:
  * This is a property which belongs to another entity. Don't confuse it with
  * an Entity with the "Property" role.
  */
-class Property {
+class Property : public ScalarProtoMessageWrapper<ProtoProperty> {
 public:
+  /**
+   * Copy constructor.
+   */
+  inline Property(const Property &other)
+    : Property(ProtoMessageWrapper<ProtoProperty>::CopyProtoMessage(other.wrapped)) {
+    CAOSDB_LOG_TRACE(logger_name) << "Property::Property(const Property &) "
+                                  << "- Copy constructor";
+  };
+
+  /**
+   * Move constructor.
+   */
+  inline Property(Property &&other) : Property() {
+    CAOSDB_LOG_TRACE(logger_name) << "Property::Property(Property  &&) "
+                                  << "- Move constructor";
+    this->wrapped = std::move(other.wrapped);
+    this->value.wrapped = this->wrapped->mutable_value();
+    this->data_type.wrapped = this->wrapped->mutable_data_type();
+
+    other.data_type.wrapped = nullptr;
+    other.value.wrapped = nullptr;
+  }
+
   explicit inline Property(ProtoProperty *other)
-    : value(Value(other->mutable_value())), data_type(DataType(other->mutable_data_type())),
-      wrapped(other){};
-  Property();
+    : ScalarProtoMessageWrapper<ProtoProperty>(other),
+      value(this->wrapped->has_value() ? this->wrapped->mutable_value()
+                                       : static_cast<ProtoValue *>(nullptr)),
+      data_type(this->wrapped->has_data_type() ? this->wrapped->mutable_data_type()
+                                               : static_cast<ProtoDataType *>(nullptr)){};
+  inline Property()
+    : ScalarProtoMessageWrapper<ProtoProperty>(), value(static_cast<ProtoValue *>(nullptr)),
+      data_type(static_cast<ProtoDataType *>(nullptr)){};
 
   /**
    * Return the id of this  property
@@ -541,16 +557,27 @@ public:
   auto SetDataType(const std::string &new_data_type, bool list_type = false) -> StatusCode;
 
   /**
-   * Return a json string representing this property.
-   *
-   * This is intended for debugging
-   */
-  inline auto ToString() const -> const std::string {
-    google::protobuf::util::JsonPrintOptions options;
-    options.add_whitespace = true;
-    std::string out;
-    google::protobuf::util::MessageToJsonString(*(this->wrapped), &out, options);
-    return out;
+   * Copy assignment operator.
+   */
+  auto operator=(const Property &other) -> Property & {
+    CAOSDB_LOG_TRACE(logger_name) << "Property::operator=(const Property &) "
+                                  << "- Copy assignment operator";
+    this->wrapped->CopyFrom(*other.wrapped);
+    this->value = Value(this->wrapped->mutable_value());
+    this->data_type = DataType(this->wrapped->mutable_data_type());
+    return *this;
+  }
+
+  /**
+   * Move assignment operator.
+   */
+  auto operator=(Property &&other) -> Property & {
+    CAOSDB_LOG_TRACE(logger_name) << "Property::operator=(Property &&) "
+                                  << "- Move assignment operator";
+    this->wrapped = std::move(other.wrapped);
+    this->value = std::move(other.value);
+    this->data_type = std::move(other.data_type);
+    return *this;
   }
 
   friend class Entity;
@@ -558,11 +585,8 @@ public:
   friend class RepeatedPtrFieldWrapper<Property, ProtoProperty>;
 
 private:
-  static auto CreateProtoProperty() -> ProtoProperty *;
   Value value;
   DataType data_type;
-
-  mutable ProtoProperty *wrapped;
 };
 
 /**
@@ -580,16 +604,15 @@ private:
  * for (auto &property : my_properties) {...}
  * \endcode
  */
-class Properties : public RepeatedPtrFieldWrapper<Property, caosdb::entity::v1alpha1::Property> {
+class Properties : public RepeatedPtrFieldWrapper<Property, ProtoProperty> {
 public:
   ~Properties() = default;
   friend class Entity;
 
 private:
   inline Properties(){};
-  explicit inline Properties(
-    ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Property> *wrapped)
-    : RepeatedPtrFieldWrapper<Property, caosdb::entity::v1alpha1::Property>(wrapped){};
+  explicit inline Properties(RepeatedPtrField<ProtoProperty> *wrapped)
+    : RepeatedPtrFieldWrapper<Property, ProtoProperty>(wrapped){};
 };
 
 /**
@@ -610,23 +633,22 @@ private:
  * response. calls Entity(), then moves the data to the wrapped ProtoEntity.
  *
  */
-class Entity {
+class Entity : public ScalarProtoMessageWrapper<ProtoEntity> {
 public:
-  inline Entity(const Entity &original) : Entity(Copy(*original.wrapped)) {
+  inline Entity(const Entity &original)
+    : Entity(ProtoMessageWrapper::CopyProtoMessage(original.wrapped)) {
     this->errors.wrapped->CopyFrom(*original.errors.wrapped);
     this->warnings.wrapped->CopyFrom(*original.warnings.wrapped);
     this->infos.wrapped->CopyFrom(*original.infos.wrapped);
   };
   explicit Entity(ProtoEntity *other)
-    : wrapped(other), value(Value(other->mutable_value())),
-      data_type(DataType(other->mutable_data_type())) {
-    data_type.wrapped = this->wrapped->mutable_data_type();
-    value.wrapped = this->wrapped->mutable_value();
+    : ScalarProtoMessageWrapper<ProtoEntity>(other),
+      value(this->wrapped->has_value() ? this->wrapped->mutable_value()
+                                       : static_cast<ProtoValue *>(nullptr)),
+      data_type(this->wrapped->has_data_type() ? this->wrapped->mutable_data_type()
+                                               : static_cast<ProtoDataType *>(nullptr)) {
     properties.wrapped = this->wrapped->mutable_properties();
     parents.wrapped = this->wrapped->mutable_parents();
-    errors.wrapped = CreateMessagesField();
-    warnings.wrapped = CreateMessagesField();
-    infos.wrapped = CreateMessagesField();
   };
   explicit inline Entity(EntityResponse *response) : Entity(response->release_entity()) {
     this->errors.wrapped->Swap(response->mutable_errors());
@@ -642,22 +664,35 @@ public:
     this->infos.wrapped->Swap(id_response->mutable_infos());
   };
 
-  explicit inline Entity() : Entity(Entity::CreateProtoEntity()){};
+  explicit inline Entity()
+    : ScalarProtoMessageWrapper<ProtoEntity>(), value(static_cast<ProtoValue *>(nullptr)),
+      data_type(static_cast<ProtoDataType *>(nullptr)) {
+    properties.wrapped = this->wrapped->mutable_properties();
+    parents.wrapped = this->wrapped->mutable_parents();
+  };
 
-  [[nodiscard]] inline auto GetId() const noexcept -> const std::string & { return wrapped->id(); };
-  [[nodiscard]] inline auto HasId() const noexcept -> bool { return !wrapped->id().empty(); }
+  [[nodiscard]] inline auto GetId() const noexcept -> const std::string & {
+    return this->wrapped->id();
+  };
+  [[nodiscard]] inline auto HasId() const noexcept -> bool { return !this->wrapped->id().empty(); }
   [[nodiscard]] inline auto GetVersionId() const -> const std::string & {
-    return wrapped->version().id();
+    return this->wrapped->version().id();
   };
 
-  [[nodiscard]] inline auto GetRole() const -> Role { return static_cast<Role>(wrapped->role()); };
-  [[nodiscard]] inline auto GetName() const -> const std::string & { return wrapped->name(); };
+  [[nodiscard]] inline auto GetRole() const -> Role {
+    return static_cast<Role>(this->wrapped->role());
+  };
+  [[nodiscard]] inline auto GetName() const -> const std::string & {
+    return this->wrapped->name();
+  };
   [[nodiscard]] inline auto GetDescription() const -> const std::string & {
-    return wrapped->description();
+    return this->wrapped->description();
   };
 
   [[nodiscard]] inline auto GetDataType() const -> const DataType & { return this->data_type; };
-  [[nodiscard]] inline auto GetUnit() const -> const std::string & { return wrapped->unit(); };
+  [[nodiscard]] inline auto GetUnit() const -> const std::string & {
+    return this->wrapped->unit();
+  };
   [[nodiscard]] inline auto GetValue() const -> const Value & { return this->value; };
 
   [[nodiscard]] auto GetParents() const -> const Parents &;
@@ -673,14 +708,6 @@ public:
   [[nodiscard]] auto GetInfos() const -> const Messages & { return infos; }
   [[nodiscard]] inline auto HasInfos() const -> bool { return this->infos.wrapped->size() > 0; }
 
-  inline auto ToString() const -> const std::string {
-    google::protobuf::util::JsonPrintOptions options;
-    options.add_whitespace = true;
-    std::string out;
-    google::protobuf::util::MessageToJsonString(*(this->wrapped), &out, options);
-    return out;
-  }
-
   auto SetRole(Role role) -> void;
   auto SetName(const std::string &name) -> void;
   /**
@@ -761,11 +788,6 @@ public:
   }
 
 private:
-  static inline auto Copy(const ProtoEntity &from) -> ProtoEntity * {
-    auto to = from.New();
-    to->CopyFrom(from);
-    return to;
-  }
   inline auto GetNextFileId() -> std::string {
     std::string str = "0123456789abcdef";
     std::mt19937 generator(std::random_device{}());
@@ -777,14 +799,12 @@ private:
     }
     return result;
   }
-  static auto CreateProtoEntity() -> ProtoEntity *;
   static auto CreateMessagesField() -> RepeatedPtrField<ProtoMessage> *;
   auto SetId(const std::string &id) -> void;
   auto SetVersionId(const std::string &id) -> void;
 
 private:
   FileDescriptor file_descriptor;
-  ProtoEntity *wrapped;
   Properties properties;
   Parents parents;
   Messages errors;
diff --git a/include/caosdb/protobuf_helper.h b/include/caosdb/protobuf_helper.h
index 8574845b194f0080d2b0f1d07475c4bf1c3f82bd..4dfc11c396ded34db6bb2cbed2c938a5402dd7c7 100644
--- a/include/caosdb/protobuf_helper.h
+++ b/include/caosdb/protobuf_helper.h
@@ -22,9 +22,9 @@
 #ifndef CAOSDB_PROTOBUF_HELPER_H
 #define CAOSDB_PROTOBUF_HELPER_H
 
-#include "caosdb/status_code.h"             // for StatusCode, SUCCESS
-#include <google/protobuf/arena.h>          // for Arena
-#include <google/protobuf/extension_set.h>  // for Arena
+#include <google/protobuf/arena.h> // for Arena
+// IWYU pragma: no_include "google/protobuf/extension_set.h"
+// IWYU pragma: no_include "google/protobuf/generated_message_util.h"
 #include <google/protobuf/util/json_util.h> // for JsonOptions, MessageToJs...
 #include <string>                           // for string
 
@@ -32,6 +32,7 @@
   std::string out;                                                                                 \
   {                                                                                                \
     google::protobuf::util::JsonOptions options;                                                   \
+    options.add_whitespace = true;                                                                 \
     google::protobuf::util::MessageToJsonString(message, &out, options);                           \
   }
 
@@ -41,27 +42,76 @@ using google::protobuf::Arena;
 
 auto get_arena() -> Arena *;
 
+/**
+ * Abstract wrapper class for Protobuf messages.
+ */
 template <typename P> class ProtoMessageWrapper {
 public:
-  ProtoMessageWrapper(const ProtoMessageWrapper &other) = default;
-  inline auto CopyFrom(const ProtoMessageWrapper &other) noexcept -> StatusCode {
-    this->wrapped->CopyFrom(*other.wrapped);
-    return StatusCode::SUCCESS;
+  virtual ~ProtoMessageWrapper() = 0;
+  /**
+   * Return a json representation of this object.
+   */
+  virtual inline auto ToString() const noexcept -> const std::string = 0;
+
+  /**
+   * Return true if the underlying Protobuf messages have the same
+   * serialization.
+   */
+  inline auto operator==(const ProtoMessageWrapper<P> &other) const noexcept -> bool {
+    if (this->wrapped != nullptr && other.wrapped != nullptr) {
+      return this->wrapped->SerializeAsString() == other.wrapped->SerializeAsString();
+    }
+    // both nullptr?
+    return this->wrapped == other.wrapped;
   }
 
   /**
-   * Return a json representation of this object.
+   * Return true if the underlying Protobuf messages have a different
+   * serialization.
    */
-  [[nodiscard]] inline auto ToString() const noexcept -> const std::string {
-    CAOSDB_DEBUG_MESSAGE_STRING(*wrapped, out)
-    return out;
+  inline auto operator!=(const ProtoMessageWrapper<P> &other) const noexcept -> bool {
+    if (this->wrapped != nullptr && other.wrapped != nullptr) {
+      return this->wrapped->SerializeAsString() != other.wrapped->SerializeAsString();
+    }
+    // only one is nullptr?
+    return this->wrapped != other.wrapped;
   }
 
 protected:
+  inline static auto CopyProtoMessage(P *wrapped) -> P * {
+    P *copy = Arena::CreateMessage<P>(get_arena());
+    copy->CopyFrom(*wrapped);
+    return copy;
+  }
   ProtoMessageWrapper() : ProtoMessageWrapper(Arena::CreateMessage<P>(get_arena())) {}
   ProtoMessageWrapper(P *wrapped) : wrapped(wrapped) {}
   P *wrapped;
 };
 
+template <typename P> ProtoMessageWrapper<P>::~ProtoMessageWrapper() = default;
+
+/**
+ * Wrapper class for scalar Protobuf messages.
+ *
+ * Scalar means in this context, any message but classes derived from
+ * RepeatedPtrField.
+ */
+template <typename P> class ScalarProtoMessageWrapper : public ProtoMessageWrapper<P> {
+public:
+  inline virtual ~ScalarProtoMessageWrapper() = default;
+  inline ScalarProtoMessageWrapper() = default;
+  inline ScalarProtoMessageWrapper(P *wrapped) : ProtoMessageWrapper<P>(wrapped) {}
+  /**
+   * Return a json representation of this object.
+   */
+  inline auto ToString() const noexcept -> const std::string override {
+    if (this->wrapped == nullptr) {
+      return "{}\n";
+    }
+    CAOSDB_DEBUG_MESSAGE_STRING(*this->wrapped, out)
+    return out;
+  }
+};
+
 } // namespace caosdb::utility
 #endif
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
index 153ee46f8173d98a13203f31cd6a140999a58689..634c7a0d01b613e4fb6ad4d56e037693d9abc2c2 100644
--- a/include/caosdb/transaction.h
+++ b/include/caosdb/transaction.h
@@ -35,7 +35,6 @@
 #include <boost/preprocessor/seq/limits/enum_256.hpp> // for BOOST_PP_SEQ_E...
 #include <boost/preprocessor/seq/limits/size_256.hpp> // for BOOST_PP_SEQ_S...
 #include <google/protobuf/arena.h>                    // for Arena
-#include <google/protobuf/generated_message_util.h>   // for CreateMessage...
 #include <google/protobuf/util/json_util.h>           // for MessageToJsonS...
 #include <grpcpp/impl/codegen/completion_queue.h>     // for CompletionQueue
 #include <algorithm>                                  // for max
diff --git a/include/caosdb/value.h b/include/caosdb/value.h
index fd70c4f837dccaeedca0c9801c2518eb111af398..8296be311a8262339b58de06d49940100fc183f3 100644
--- a/include/caosdb/value.h
+++ b/include/caosdb/value.h
@@ -31,7 +31,8 @@
 #include <vector>                           // for vector
 
 #define LIST_VALUE_CONSTRUCTOR(TYPE, SETTER)                                                       \
-  explicit inline Value(const std::vector<TYPE> &values) : ProtoMessageWrapper<ProtoValue>() {     \
+  explicit inline Value(const std::vector<TYPE> &values)                                           \
+    : ScalarProtoMessageWrapper<ProtoValue>() {                                                    \
     for (const auto &value : values) {                                                             \
       this->wrapped->mutable_list_values()->add_values()->SETTER(value);                           \
     }                                                                                              \
@@ -40,6 +41,7 @@
 namespace caosdb::entity {
 using caosdb::utility::get_arena;
 using caosdb::utility::ProtoMessageWrapper;
+using caosdb::utility::ScalarProtoMessageWrapper;
 using google::protobuf::Arena;
 using ProtoSpecialValue = caosdb::entity::v1alpha1::SpecialValue;
 using ProtoValue = caosdb::entity::v1alpha1::Value;
@@ -167,7 +169,7 @@ protected:
 
 inline AbstractValue::~AbstractValue() {}
 
-class ScalarValue : public AbstractValue, public ProtoMessageWrapper<ProtoScalarValue> {
+class ScalarValue : public AbstractValue, public ScalarProtoMessageWrapper<ProtoScalarValue> {
 public:
   /**
    * Destructor.
@@ -203,37 +205,39 @@ public:
   }
 
   inline ScalarValue(ProtoScalarValue *wrapped)
-    : ProtoMessageWrapper<ProtoScalarValue>(wrapped), proto_value(nullptr) {}
+    : ScalarProtoMessageWrapper<ProtoScalarValue>(wrapped), proto_value(nullptr) {}
 
   [[nodiscard]] inline auto IsNull() const noexcept -> bool {
-    return (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue &&
+    return this->wrapped == nullptr ||
+           (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue &&
             this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED);
   }
   [[nodiscard]] inline auto IsString() const noexcept -> bool {
-    return (this->wrapped->scalar_value_case() == ScalarValueCase::kStringValue) ||
-           (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue &&
-            this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING);
+    return !IsNull() &&
+           ((this->wrapped->scalar_value_case() == ScalarValueCase::kStringValue) ||
+            (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue &&
+             this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING));
   }
   [[nodiscard]] inline auto GetAsString() const noexcept -> const std::string & {
     return this->wrapped->string_value();
   }
 
   [[nodiscard]] inline auto IsDouble() const noexcept -> bool {
-    return (this->wrapped->scalar_value_case() == ScalarValueCase::kDoubleValue);
+    return !IsNull() && (this->wrapped->scalar_value_case() == ScalarValueCase::kDoubleValue);
   }
   [[nodiscard]] inline auto GetAsDouble() const noexcept -> double {
     return this->wrapped->double_value();
   }
 
   [[nodiscard]] inline auto IsInt64() const noexcept -> bool {
-    return (this->wrapped->scalar_value_case() == ScalarValueCase::kIntegerValue);
+    return !IsNull() && (this->wrapped->scalar_value_case() == ScalarValueCase::kIntegerValue);
   }
   [[nodiscard]] inline auto GetAsInt64() const noexcept -> int64_t {
     return this->wrapped->integer_value();
   }
 
   [[nodiscard]] inline auto IsBool() const noexcept -> bool {
-    return (this->wrapped->scalar_value_case() == ScalarValueCase::kBooleanValue);
+    return !IsNull() && (this->wrapped->scalar_value_case() == ScalarValueCase::kBooleanValue);
   }
   [[nodiscard]] inline auto GetAsBool() const noexcept -> bool {
     return this->wrapped->boolean_value();
@@ -248,7 +252,7 @@ public:
     return empty_collection;
   }
   [[nodiscard]] inline auto ToString() const noexcept -> const std::string {
-    return ProtoMessageWrapper::ToString();
+    return ScalarProtoMessageWrapper::ToString();
   }
 
   friend class Value;
@@ -261,18 +265,20 @@ protected:
     }
     return this->proto_value;
   };
-  inline ScalarValue() : ProtoMessageWrapper<ProtoScalarValue>(), proto_value(nullptr) {}
+  inline ScalarValue() : ScalarProtoMessageWrapper<ProtoScalarValue>(), proto_value(nullptr) {}
 
 private:
   mutable ProtoValue *proto_value;
 };
 
-class Value : public AbstractValue, public ProtoMessageWrapper<ProtoValue> {
+class Value : public AbstractValue, public ScalarProtoMessageWrapper<ProtoValue> {
 public:
   /**
    * Copy constructor.
    */
-  inline Value(const Value &original) : Value() { this->wrapped->CopyFrom(*original.wrapped); }
+  inline Value(const Value &original) : ScalarProtoMessageWrapper<ProtoValue>() {
+    this->wrapped->CopyFrom(*original.wrapped);
+  }
   /**
    * Move constructor.
    */
@@ -281,30 +287,30 @@ public:
    * Destructor.
    */
   inline ~Value() {}
-  inline Value() : ProtoMessageWrapper<ProtoValue>() {
+  inline Value() : ScalarProtoMessageWrapper<ProtoValue>(static_cast<ProtoValue *>(nullptr)) {
     // has NULL_VALUE now
   }
-  explicit inline Value(const ScalarValue &value) : Value() {
+  explicit inline Value(const ScalarValue &value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->CopyFrom(*value.wrapped);
   }
-  explicit inline Value(const AbstractValue &value) : Value() {
+  explicit inline Value(const AbstractValue &value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->CopyFrom(*value.GetProtoValue());
   }
-  explicit inline Value(ProtoValue *wrapped) : ProtoMessageWrapper<ProtoValue>(wrapped) {}
-  explicit inline Value(const std::string &value) : ProtoMessageWrapper<ProtoValue>() {
+  explicit inline Value(ProtoValue *wrapped) : ScalarProtoMessageWrapper<ProtoValue>(wrapped) {}
+  explicit inline Value(const std::string &value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->set_string_value(value);
   }
-  explicit inline Value(const char *value) : ProtoMessageWrapper<ProtoValue>() {
+  explicit inline Value(const char *value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->set_string_value(std::string(value));
   }
-  explicit inline Value(double value) : ProtoMessageWrapper<ProtoValue>() {
+  explicit inline Value(double value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->set_double_value(value);
   }
-  explicit inline Value(int64_t value) : ProtoMessageWrapper<ProtoValue>() {
+  explicit inline Value(int64_t value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->set_integer_value(value);
   }
   explicit inline Value(int value) : Value(static_cast<int64_t>(value)) {}
-  explicit inline Value(bool value) : ProtoMessageWrapper<ProtoValue>() {
+  explicit inline Value(bool value) : ScalarProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->set_boolean_value(value);
   }
 
@@ -316,14 +322,15 @@ public:
   LIST_VALUE_CONSTRUCTOR(bool, set_boolean_value)
 
   [[nodiscard]] inline auto IsNull() const noexcept -> bool {
-    return this->wrapped->value_case() == ValueCase::VALUE_NOT_SET ||
-           (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kSpecialValue &&
-            this->wrapped->scalar_value().special_value() ==
-              ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED);
+    return this->wrapped == nullptr ||
+           (this->wrapped->value_case() == ValueCase::VALUE_NOT_SET ||
+            (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kSpecialValue &&
+             this->wrapped->scalar_value().special_value() ==
+               ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED));
   }
 
   [[nodiscard]] inline auto IsString() const noexcept -> bool {
-    if (this->wrapped->value_case() == ValueCase::kScalarValue) {
+    if (!IsNull() && this->wrapped->value_case() == ValueCase::kScalarValue) {
 
       return (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kStringValue) ||
              (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kSpecialValue &&
@@ -333,50 +340,60 @@ public:
     return false;
   }
   [[nodiscard]] inline auto GetAsString() const noexcept -> const std::string & {
+    if (!IsString()) {
+      static std::string empty_string;
+      return empty_string;
+    }
     return this->wrapped->scalar_value().string_value();
-    ;
   }
 
   [[nodiscard]] inline auto IsDouble() const noexcept -> bool {
-    if (this->wrapped->value_case() == ValueCase::kScalarValue) {
+    if (!IsNull() && this->wrapped->value_case() == ValueCase::kScalarValue) {
 
       return (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kDoubleValue);
     }
     return false;
   }
   [[nodiscard]] inline auto GetAsDouble() const noexcept -> double {
+    if (!IsDouble())
+      return 0.0;
     return this->wrapped->scalar_value().double_value();
   }
 
   [[nodiscard]] inline auto IsInt64() const noexcept -> bool {
-    if (this->wrapped->value_case() == ValueCase::kScalarValue) {
+    if (!IsNull() && this->wrapped->value_case() == ValueCase::kScalarValue) {
 
       return (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kIntegerValue);
     }
     return false;
   }
   [[nodiscard]] inline auto GetAsInt64() const noexcept -> int64_t {
+    if (!IsInt64())
+      return 0;
     return this->wrapped->scalar_value().integer_value();
   }
 
   [[nodiscard]] inline auto IsBool() const noexcept -> bool {
-    if (this->wrapped->value_case() == ValueCase::kScalarValue) {
+    if (!IsNull() && this->wrapped->value_case() == ValueCase::kScalarValue) {
 
       return (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kBooleanValue);
     }
     return false;
   }
   [[nodiscard]] inline auto GetAsBool() const noexcept -> bool {
+    if (!IsBool())
+      return false;
     return this->wrapped->scalar_value().boolean_value();
   }
 
   [[nodiscard]] inline auto IsVector() const noexcept -> bool {
-    return this->wrapped->value_case() == ValueCase::kListValues;
+    return !IsNull() && this->wrapped->value_case() == ValueCase::kListValues;
   }
   [[nodiscard]] inline auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & {
     if (!IsVector()) {
       // create empty list
-      this->collection_values = std::make_unique<std::vector<ScalarValue>>();
+      static std::vector<ScalarValue> empty_values;
+      return empty_values;
     }
     if (this->collection_values == nullptr) {
       this->collection_values = std::make_unique<std::vector<ScalarValue>>();
@@ -387,20 +404,13 @@ public:
     return *(this->collection_values);
   }
 
-  /**
-   * Return true if the underlying Protobuf messages have the same
-   * serialization.
-   */
-  inline auto operator==(const Value &other) const noexcept -> bool {
-    return this->wrapped->SerializeAsString() == other.wrapped->SerializeAsString();
-  }
-
   /**
    * Copy assignment operator.
    */
   inline auto operator=(const Value &other) -> Value & {
     if (&other != this) {
       this->wrapped->CopyFrom(*other.wrapped);
+      this->collection_values.reset();
     }
     return *this;
   }
@@ -411,12 +421,13 @@ public:
   inline auto operator=(Value &&other) -> Value & {
     if (&other != this) {
       this->wrapped = std::move(other.wrapped);
+      this->collection_values.reset();
     }
     return *this;
   }
 
   [[nodiscard]] inline auto ToString() const noexcept -> const std::string {
-    return ProtoMessageWrapper::ToString();
+    return ScalarProtoMessageWrapper::ToString();
   }
 
   friend class Entity;
diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp
index c409a46e8ad37aee93b67b2a36109b1fe8f8c640..bb73431e4af00ed1db48b8f87fa883db6f4f9b31 100644
--- a/src/caosdb/entity.cpp
+++ b/src/caosdb/entity.cpp
@@ -42,18 +42,6 @@ Messages::~Messages() = default;
 
 // Parent /////////////////////////////////////////////////////////////////////
 
-Parent::Parent() : wrapped(Parent::CreateProtoParent()) {
-  // TODO(fspreck) Re-enable once we have decided how to attach
-  // messages to parents.
-  // errors.wrapped = this->wrapped->mutable_errors();
-  // warnings.wrapped = this->wrapped->mutable_warnings();
-  // infos.wrapped = this->wrapped->mutable_infos();
-}
-
-auto Parent::CreateProtoParent() -> ProtoParent * {
-  return Arena::CreateMessage<ProtoParent>(get_arena());
-}
-
 auto Parent::SetName(const std::string &name) -> void { this->wrapped->set_name(name); }
 
 auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); }
@@ -66,11 +54,7 @@ auto Parent::SetId(const std::string &id) -> void { this->wrapped->set_id(id); }
   return this->wrapped->description();
 }
 
-Property::Property() : Property(Property::CreateProtoProperty()) {}
-
-auto Property::CreateProtoProperty() -> ProtoProperty * {
-  return Arena::CreateMessage<ProtoProperty>(get_arena());
-}
+// Property ///////////////////////////////////////////////////////////////////
 
 [[nodiscard]] auto Property::GetId() const -> const std::string & { return this->wrapped->id(); }
 
@@ -106,7 +90,16 @@ auto Property::SetImportance(Importance importance) -> void {
   this->wrapped->set_importance(static_cast<ProtoImportance>(importance));
 }
 
-auto Property::SetValue(const Value &value) -> StatusCode { return this->value.CopyFrom(value); }
+auto Property::SetValue(const Value &value) -> StatusCode {
+  if (value.wrapped == nullptr) {
+    this->wrapped->clear_value();
+    this->value = Value();
+  } else {
+    this->wrapped->mutable_value()->CopyFrom(*value.wrapped);
+    this->value = Value(this->wrapped->mutable_value());
+  }
+  return StatusCode::SUCCESS;
+}
 
 auto Property::SetValue(const AbstractValue &value) -> StatusCode { return SetValue(Value(value)); }
 
@@ -149,7 +142,14 @@ auto Property::SetValue(const bool value) -> StatusCode { return SetValue(Value(
 auto Property::SetUnit(const std::string &unit) -> void { this->wrapped->set_unit(unit); }
 
 auto Property::SetDataType(const DataType &new_data_type) -> StatusCode {
-  return this->data_type.CopyFrom(new_data_type);
+  if (new_data_type.wrapped == nullptr) {
+    this->wrapped->clear_data_type();
+    this->data_type = DataType();
+  } else {
+    this->wrapped->mutable_data_type()->CopyFrom(*new_data_type.wrapped);
+    this->data_type = DataType(this->wrapped->mutable_data_type());
+  }
+  return StatusCode::SUCCESS;
 }
 
 auto Property::SetDataType(const AtomicDataType new_data_type, bool list_type) -> StatusCode {
@@ -173,10 +173,6 @@ auto Entity::AppendProperty(const Property &property) -> void { this->properties
 
 auto Entity::RemoveProperty(int index) -> void { this->properties.Remove(index); }
 
-auto Entity::CreateProtoEntity() -> ProtoEntity * {
-  return Arena::CreateMessage<ProtoEntity>(get_arena());
-}
-
 auto Entity::CreateMessagesField() -> RepeatedPtrField<ProtoMessage> * {
   return Arena::CreateMessage<RepeatedPtrField<ProtoMessage>>(get_arena());
 }
@@ -201,7 +197,14 @@ auto Entity::SetValue(const Value &value) -> StatusCode {
   if (GetRole() != Role::PROPERTY) {
     return StatusCode::ENTITY_CANNOT_HAVE_A_VALUE;
   }
-  return this->value.CopyFrom(value);
+  if (value.wrapped == nullptr) {
+    this->wrapped->clear_value();
+    this->value = Value();
+  } else {
+    this->wrapped->mutable_value()->CopyFrom(*value.wrapped);
+    this->value = Value(this->wrapped->mutable_value());
+  }
+  return StatusCode::SUCCESS;
 }
 
 auto Entity::SetValue(const AbstractValue &value) -> StatusCode { return SetValue(Value(value)); }
@@ -248,7 +251,14 @@ auto Entity::SetDataType(const DataType &new_data_type) -> StatusCode {
   if (GetRole() != Role::PROPERTY) {
     return StatusCode::ENTITY_CANNOT_HAVE_A_DATA_TYPE;
   }
-  return this->data_type.CopyFrom(new_data_type);
+  if (new_data_type.wrapped == nullptr) {
+    this->wrapped->clear_data_type();
+    this->data_type = DataType();
+  } else {
+    this->wrapped->mutable_data_type()->CopyFrom(*new_data_type.wrapped);
+    this->data_type = DataType(this->wrapped->mutable_data_type());
+  }
+  return StatusCode::SUCCESS;
 }
 
 auto Entity::SetDataType(const AtomicDataType new_data_type, bool list_type) -> StatusCode {
diff --git a/src/caosdb/file_transmission/download_request_handler.cpp b/src/caosdb/file_transmission/download_request_handler.cpp
index a157754b337e798fc4c144cb44abea153053ed79..cbe79e93eb89261d1d85484cf39f4ad5deecba18 100644
--- a/src/caosdb/file_transmission/download_request_handler.cpp
+++ b/src/caosdb/file_transmission/download_request_handler.cpp
@@ -59,17 +59,16 @@
 #include <boost/preprocessor/seq/limits/size_256.hpp>  // for BOOST_PP_SEQ_...
 #include <exception>                                   // IWYU pragma: keep
 // IWYU pragma: no_include <bits/exception.h>
-#include <google/protobuf/arena.h>                  // for Arena
-#include <google/protobuf/generated_message_util.h> // for Arena
-#include <grpcpp/impl/codegen/async_stream.h>       // for ClientAsyncRe...
-#include <grpcpp/impl/codegen/client_context.h>     // for ClientContext
-#include <grpcpp/impl/codegen/completion_queue.h>   // for CompletionQueue
-#include <grpcpp/impl/codegen/status.h>             // for Status
-#include <grpcpp/impl/codegen/status_code_enum.h>   // for OK, UNAUTHENT...
-#include <iostream>                                 // for char_traits
-#include <stdexcept>                                // for runtime_error
-#include <string>                                   // for string, opera...
-#include <utility>                                  // for move
+#include <google/protobuf/arena.h>                // for Arena
+#include <grpcpp/impl/codegen/async_stream.h>     // for ClientAsyncRe...
+#include <grpcpp/impl/codegen/client_context.h>   // for ClientContext
+#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue
+#include <grpcpp/impl/codegen/status.h>           // for Status
+#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT...
+#include <iostream>                               // for char_traits
+#include <stdexcept>                              // for runtime_error
+#include <string>                                 // for string, opera...
+#include <utility>                                // for move
 
 namespace caosdb::transaction {
 using caosdb::StatusCode;
diff --git a/src/caosdb/file_transmission/upload_request_handler.cpp b/src/caosdb/file_transmission/upload_request_handler.cpp
index cc8170249b932fb8bfa572fd345746bbbacc16af..bc74391753a0c7c3dce01f80d8fa67acc9e782cd 100644
--- a/src/caosdb/file_transmission/upload_request_handler.cpp
+++ b/src/caosdb/file_transmission/upload_request_handler.cpp
@@ -61,17 +61,16 @@
 #include <cstdint>                                     // for uint64_t
 #include <exception>                                   // IWYU pragma: keep
 // IWYU pragma: no_include <bits/exception.h>
-#include <google/protobuf/arena.h>                  // for Arena
-#include <google/protobuf/generated_message_util.h> // for CreateMessage...
-#include <grpcpp/impl/codegen/async_stream.h>       // for ClientAsyncWr...
-#include <grpcpp/impl/codegen/call_op_set.h>        // for WriteOptions
-#include <grpcpp/impl/codegen/client_context.h>     // for ClientContext
-#include <grpcpp/impl/codegen/completion_queue.h>   // for CompletionQueue
-#include <grpcpp/impl/codegen/status.h>             // for Status
-#include <grpcpp/impl/codegen/status_code_enum.h>   // for OK, UNAUTHENT...
-#include <iostream>                                 // for endl, streamsize
-#include <string>                                   // for basic_string
-#include <utility>                                  // for move
+#include <google/protobuf/arena.h>                // for Arena
+#include <grpcpp/impl/codegen/async_stream.h>     // for ClientAsyncWr...
+#include <grpcpp/impl/codegen/call_op_set.h>      // for WriteOptions
+#include <grpcpp/impl/codegen/client_context.h>   // for ClientContext
+#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue
+#include <grpcpp/impl/codegen/status.h>           // for Status
+#include <grpcpp/impl/codegen/status_code_enum.h> // for OK, UNAUTHENT...
+#include <iostream>                               // for endl, streamsize
+#include <string>                                 // for basic_string
+#include <utility>                                // for move
 
 namespace caosdb::transaction {
 using caosdb::StatusCode;
diff --git a/src/caosdb/protobuf_helper.cpp b/src/caosdb/protobuf_helper.cpp
index e8bbd07834ead9b561c7e8769ed834527337f7a6..418d14b9c847bc204582f6165fae81bf6adcc156 100644
--- a/src/caosdb/protobuf_helper.cpp
+++ b/src/caosdb/protobuf_helper.cpp
@@ -19,8 +19,7 @@
  *
  */
 #include "caosdb/protobuf_helper.h"
-#include <google/protobuf/arena.h>         // for Arena
-#include <google/protobuf/extension_set.h> // for Arena
+#include <google/protobuf/arena.h> // for Arena
 
 namespace caosdb::utility {
 
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index 678d6fbd728cdd82158e263a476f4ce63bc19310..002d20d792b49bfb88b160cb39e7d8a8e6a5bd01 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -35,15 +35,14 @@
 #include <boost/preprocessor/seq/limits/enum_256.hpp>              // for BOOST_PP_SEQ_...
 #include <boost/preprocessor/seq/limits/size_256.hpp>              // for BOOST_PP_SEQ_...
 // IWYU pragma: no_include <bits/exception.h>
-#include <exception>                                // IWYU pragma: keep
-#include <google/protobuf/arena.h>                  // for Arena
-#include <google/protobuf/generated_message_util.h> // for CreateMessage...
-#include <grpc/impl/codegen/gpr_types.h>            // for gpr_timespec
-#include <grpcpp/impl/codegen/completion_queue.h>   // for CompletionQueue
-#include <iosfwd>                                   // for streamsize
-#include <map>                                      // for map, operator!=
-#include <memory>                                   // for unique_ptr
-#include <utility>                                  // for move, pair
+#include <exception>                              // IWYU pragma: keep
+#include <google/protobuf/arena.h>                // for Arena
+#include <grpc/impl/codegen/gpr_types.h>          // for gpr_timespec
+#include <grpcpp/impl/codegen/completion_queue.h> // for CompletionQueue
+#include <iosfwd>                                 // for streamsize
+#include <map>                                    // for map, operator!=
+#include <memory>                                 // for unique_ptr
+#include <utility>                                // for move, pair
 
 namespace caosdb::transaction {
 using caosdb::entity::v1alpha1::EntityTransactionService;
diff --git a/test/test_data_type.cpp b/test/test_data_type.cpp
index 51624a7e0513f4654f2eaa9fdf3519399726199d..78e5100f3ce984ece46918df4aca9ffb8f1d2522 100644
--- a/test/test_data_type.cpp
+++ b/test/test_data_type.cpp
@@ -117,4 +117,16 @@ TEST(test_data_type, test_list_of_reference) {
   EXPECT_EQ(list_data_type.GetReferenceDataType().GetName(), "person");
 }
 
+TEST(test_data_type, test_data_type_to_string) {
+  DataType data_type1;
+  EXPECT_EQ(data_type1.ToString(), "{}\n");
+
+  ProtoDataType proto_data_type;
+  DataType data_type2(&proto_data_type);
+  EXPECT_EQ(data_type2.ToString(), "{}\n");
+
+  DataType data_type3(AtomicDataType::INTEGER);
+  EXPECT_EQ(data_type3.ToString(), "{\n \"atomicDataType\": \"ATOMIC_DATA_TYPE_INTEGER\"\n}\n");
+}
+
 } // namespace caosdb::entity
diff --git a/test/test_entity.cpp b/test/test_entity.cpp
index 75adc7e7a051cc1e2a154d6b3723e44123e28130..de0206f8b547884b7ae438b3bd4873230b9128d8 100644
--- a/test/test_entity.cpp
+++ b/test/test_entity.cpp
@@ -38,6 +38,7 @@
 #include <memory>                                // for allocator, shared_ptr
 #include <stdexcept>                             // for out_of_range
 #include <string>                                // for operator+, to_string
+#include <utility>                               // for move
 
 namespace caosdb::entity {
 using caosdb::entity::v1alpha1::IdResponse;
@@ -132,11 +133,88 @@ TEST(test_entity, test_append_property) {
   EXPECT_EQ(prop.GetName(), same_prop.GetName());
   EXPECT_EQ(prop.GetId(), same_prop.GetId());
   EXPECT_EQ(prop.GetImportance(), same_prop.GetImportance());
-  EXPECT_EQ(prop.GetValue(), same_prop.GetValue());
+  EXPECT_EQ(prop.GetValue().ToString(), same_prop.GetValue().ToString());
   EXPECT_EQ(prop.GetUnit(), same_prop.GetUnit());
   EXPECT_EQ(prop.GetDataType(), same_prop.GetDataType());
 }
 
+TEST(test_entity, test_property_copy_constructor) {
+  Property prop;
+  prop.SetName("prop_name");
+  prop.SetId("prop_id");
+  prop.SetImportance(Importance::RECOMMENDED);
+  prop.SetValue("prop_value");
+  prop.SetUnit("prop_unit");
+  prop.SetDataType("prop_dtype");
+
+  Property other_prop(prop);
+
+  EXPECT_EQ(prop, other_prop);
+  EXPECT_EQ(prop.GetName(), other_prop.GetName());
+  EXPECT_EQ(prop.GetId(), other_prop.GetId());
+  EXPECT_EQ(prop.GetImportance(), other_prop.GetImportance());
+  EXPECT_EQ(prop.GetValue().ToString(), other_prop.GetValue().ToString());
+  EXPECT_EQ(prop.GetUnit(), other_prop.GetUnit());
+  EXPECT_EQ(prop.GetDataType(), other_prop.GetDataType());
+}
+
+TEST(test_entity, test_property_copy_assignment) {
+  Property prop;
+  prop.SetName("prop_name");
+  prop.SetId("prop_id");
+  prop.SetImportance(Importance::RECOMMENDED);
+  prop.SetValue("prop_value");
+  prop.SetUnit("prop_unit");
+  prop.SetDataType("prop_dtype");
+
+  auto other_prop = prop;
+  EXPECT_EQ(prop, other_prop);
+  EXPECT_EQ(prop.GetName(), other_prop.GetName());
+  EXPECT_EQ(prop.GetId(), other_prop.GetId());
+  EXPECT_EQ(prop.GetImportance(), other_prop.GetImportance());
+  EXPECT_EQ(prop.GetValue().ToString(), other_prop.GetValue().ToString());
+  EXPECT_EQ(prop.GetUnit(), other_prop.GetUnit());
+  EXPECT_EQ(prop.GetDataType(), other_prop.GetDataType());
+
+  other_prop.SetName("other_prop_name");
+  EXPECT_NE(prop, other_prop);
+  EXPECT_NE(prop.GetName(), other_prop.GetName());
+  EXPECT_EQ(prop.GetName(), "prop_name");
+  EXPECT_EQ(other_prop.GetName(), "other_prop_name");
+}
+
+TEST(test_entity, test_property_move_assignment) {
+  Property prop;
+  prop.SetName("prop_name");
+  prop.SetId("prop_id");
+  prop.SetImportance(Importance::RECOMMENDED);
+  prop.SetValue("prop_value");
+  prop.SetUnit("prop_unit");
+  prop.SetDataType("prop_dtype");
+
+  // we compare the moved one with this one
+  const Property copy_prop(prop);
+
+  Property other_prop = std::move(prop);
+  // EXPECT_NE(prop, copy_prop);  NOLINT
+  // EXPECT_NE(prop, other_prop);  NOLINT
+  // EXPECT_EQ(prop.ToString(), "{}"); NOLINT
+
+  EXPECT_EQ(copy_prop, other_prop);
+  EXPECT_EQ(copy_prop.GetName(), other_prop.GetName());
+  EXPECT_EQ(copy_prop.GetId(), other_prop.GetId());
+  EXPECT_EQ(copy_prop.GetImportance(), other_prop.GetImportance());
+  EXPECT_EQ(copy_prop.GetValue().ToString(), other_prop.GetValue().ToString());
+  EXPECT_EQ(copy_prop.GetUnit(), other_prop.GetUnit());
+  EXPECT_EQ(copy_prop.GetDataType(), other_prop.GetDataType());
+
+  other_prop.SetName("other_prop_name");
+  EXPECT_NE(copy_prop, other_prop);
+  EXPECT_NE(copy_prop.GetName(), other_prop.GetName());
+  EXPECT_EQ(copy_prop.GetName(), "prop_name");
+  EXPECT_EQ(other_prop.GetName(), "other_prop_name");
+}
+
 TEST(test_entity, test_copy_to) {
   auto entity = Entity();
   entity.SetRole(Role::RECORD);
@@ -363,12 +441,39 @@ TEST(test_entity, test_remove_property) {
   // P0,P1,P2,   P4,P5,P6,P7,P8,P9
   entity.RemoveProperty(3);
 
+  ASSERT_EQ(entity.GetProperties().size(), 9);
+  for (int i = 0; i < 3; i++) {
+    auto name = "PROPERTY-" + std::to_string(i);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+  for (int i = 3; i < 9; i++) {
+    auto name = "PROPERTY-" + std::to_string(i + 1);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+
   // Remove at index 6
   // P0,P1,P2,   P4,P5,P6,P7,P8,P9
   //                      ^
   // P0,P1,P2,   P4,P5,P6,   P8,P9
   entity.RemoveProperty(6);
   ASSERT_EQ(entity.GetProperties().size(), 8);
+  for (int i = 0; i < 3; i++) {
+    auto name = "PROPERTY-" + std::to_string(i);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+  for (int i = 3; i < 6; i++) {
+    auto name = "PROPERTY-" + std::to_string(i + 1);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
+  for (int i = 6; i < 8; i++) {
+    auto name = "PROPERTY-" + std::to_string(i + 2);
+    const auto &property = entity.GetProperties().at(i);
+    EXPECT_EQ(property.GetName(), name);
+  }
 
   // AppendProperty another property
   // P0,P1,P2,   P4,P5,P6,   P8,P9
@@ -460,4 +565,91 @@ TEST(test_entity, test_add_file) {
   EXPECT_EQ(entity.SetLocalPath(TEST_DATA_DIR + "/test.json"), StatusCode::SUCCESS);
 }
 
+TEST(test_entity, test_entity_to_string) {
+  Entity entity;
+  EXPECT_EQ(entity.ToString(), "{}\n");
+
+  entity.SetRole(Role::PROPERTY);
+  EXPECT_EQ(entity.ToString(), "{\n \"role\": \"ENTITY_ROLE_PROPERTY\"\n}\n");
+
+  entity.SetValue(Value());
+  EXPECT_EQ(entity.ToString(), "{\n \"role\": \"ENTITY_ROLE_PROPERTY\"\n}\n");
+
+  entity.SetDataType(DataType());
+  EXPECT_EQ(entity.ToString(), "{\n \"role\": \"ENTITY_ROLE_PROPERTY\"\n}\n");
+}
+
+TEST(test_entity, test_properties_to_string) {
+  Entity entity;
+  EXPECT_EQ(entity.GetProperties().ToString(), "[]\n");
+
+  Property property;
+  property.SetName("Prop1");
+  entity.AppendProperty(property);
+  EXPECT_EQ(entity.GetProperties().ToString(), "[\n{\n \"name\": \"Prop1\"\n}\n]\n");
+
+  Property property2;
+  property2.SetName("Prop2");
+  entity.AppendProperty(property2);
+  EXPECT_EQ(entity.GetProperties().ToString(),
+            "[\n{\n \"name\": \"Prop1\"\n},\n{\n \"name\": \"Prop2\"\n}\n]\n");
+}
+
+TEST(test_entity, test_property_to_string) {
+  Property property;
+  EXPECT_EQ(property.ToString(), "{}\n");
+
+  property.SetDataType(AtomicDataType::DOUBLE);
+  EXPECT_EQ(property.ToString(),
+            "{\n \"dataType\": {\n  \"atomicDataType\": \"ATOMIC_DATA_TYPE_DOUBLE\"\n }\n}\n");
+}
+
+TEST(test_entity, test_parents_to_string) {
+  Parent parent;
+  parent.SetName("the name");
+
+  Entity entity;
+  entity.AppendParent(parent);
+
+  EXPECT_EQ(entity.GetParents().ToString(), "[\n{\n \"name\": \"the name\"\n}\n]\n");
+}
+
+TEST(test_entity, test_parent_to_string) {
+  Parent parent;
+  EXPECT_EQ(parent.ToString(), "{}\n");
+
+  parent.SetName("the name");
+  EXPECT_EQ(parent.ToString(), "{\n \"name\": \"the name\"\n}\n");
+}
+
+TEST(test_entity, test_messages_to_string) {
+  IdResponse idResponse;
+  idResponse.set_id("entity_id");
+  auto *error = idResponse.add_errors();
+  error->set_code(MessageCode::ENTITY_DOES_NOT_EXIST);
+  error->set_description("error_desc");
+
+  Entity entity(&idResponse);
+
+  // Messages are not printed, currently.
+  EXPECT_EQ(entity.ToString(), "{\n \"id\": \"entity_id\",\n \"version\": {}\n}\n");
+  EXPECT_EQ(entity.GetErrors().ToString(),
+            "[\n{\n \"code\": 2,\n \"description\": \"error_desc\"\n}\n]\n");
+}
+
+TEST(test_entity, test_message_to_string) {
+  IdResponse idResponse;
+  idResponse.set_id("entity_id");
+  auto *error = idResponse.add_errors();
+  error->set_code(MessageCode::ENTITY_DOES_NOT_EXIST);
+  error->set_description("error_desc");
+
+  Entity entity(&idResponse);
+
+  // Messages are not printed, currently.
+  EXPECT_EQ(entity.ToString(), "{\n \"id\": \"entity_id\",\n \"version\": {}\n}\n");
+  EXPECT_EQ(entity.GetErrors().at(0).ToString(),
+            "{\n \"code\": 2,\n \"description\": \"error_desc\"\n}\n");
+}
+
 } // namespace caosdb::entity
diff --git a/test/test_list_properties.cpp b/test/test_list_properties.cpp
index 27d0aad53cb9eb1342e1cb87ca24d7c3b71938f0..bc82896f5e7623643cb249979f381f3a7097d40d 100644
--- a/test/test_list_properties.cpp
+++ b/test/test_list_properties.cpp
@@ -24,6 +24,7 @@
 #include "caosdb/entity.h"                  // for Entity
 #include "caosdb/entity/v1alpha1/main.pb.h" // for AtomicDataType, DataType
 #include "caosdb/value.h"                   // for Value
+#include <cstdint>                          // for int64_t
 #include <gtest/gtest-message.h>            // for Message
 #include <gtest/gtest-test-part.h>          // for TestPartResult, SuiteApi...
 #include <gtest/gtest_pred_impl.h>          // for AssertionResult, Test
@@ -61,4 +62,39 @@ TEST(test_list_property, test_list_of_text) {
   EXPECT_EQ(value.GetAsVector().at(1).GetAsString(), "item2");
 }
 
+TEST(test_list_property, test_list_reassignment) {
+  Property list_property;
+  // assign int list
+  std::vector<int64_t> int_values{1, 2, 3};
+  list_property.SetValue(int_values);
+  const auto &value_ints = list_property.GetValue();
+  EXPECT_TRUE(value_ints.IsVector());
+  EXPECT_EQ(value_ints.GetAsVector().size(), 3);
+  for (int ii = 0; ii < 3; ii++) {
+    EXPECT_TRUE(value_ints.GetAsVector().at(ii).IsInt64());
+    EXPECT_EQ(value_ints.GetAsVector().at(ii).GetAsInt64(), int_values[ii]);
+  }
+
+  // Re-assign to double scalar
+  double double_value(1.23);
+  list_property.SetValue(double_value);
+  const auto &value_double = list_property.GetValue();
+  EXPECT_FALSE(value_double.IsVector());
+  EXPECT_TRUE(value_double.IsDouble());
+  EXPECT_FALSE(value_double.IsInt64());
+  EXPECT_EQ(value_double.GetAsDouble(), double_value);
+
+  // Re-assign to boolean list
+  std::vector<bool> bool_values{true, false, false, true};
+  list_property.SetValue(bool_values);
+  const auto &value_bools = list_property.GetValue();
+  EXPECT_TRUE(value_bools.IsVector());
+  EXPECT_EQ(value_bools.GetAsVector().size(), 4);
+  for (int jj = 0; jj < 4; jj++) {
+    EXPECT_TRUE(value_bools.GetAsVector().at(jj).IsBool());
+    EXPECT_FALSE(value_bools.GetAsVector().at(jj).IsInt64());
+    EXPECT_EQ(value_bools.GetAsVector().at(jj).GetAsBool(), bool_values[jj]);
+  }
+}
+
 } // namespace caosdb::entity
diff --git a/test/test_utility.cpp b/test/test_utility.cpp
index 875cfbfcf199100d4a03d847680c997ecc4cb582..3a9a420cee742c1001c727a6154452ee10e8d8fb 100644
--- a/test/test_utility.cpp
+++ b/test/test_utility.cpp
@@ -26,6 +26,7 @@
 #include "boost/json/value.hpp"               // for value
 #include "caosdb/data_type.h"                 // for atomicdatatype_names
 #include "caosdb/entity.h"                    // for importance_names, role...
+#include "caosdb/status_code.h"               // for get_status_description
 #include "caosdb/utility.h"                   // for base64_encode, load_js...
 #include "caosdb_test_utility.h"              // for TEST_DATA_DIR
 #include <gtest/gtest-message.h>              // for Message
@@ -78,4 +79,10 @@ TEST(test_utility, enum_names) {
                        std::out_of_range, "Could not find enum value for string 'Invalid name'.");
 }
 
+TEST(test_utility, test_status_code_description) {
+  EXPECT_EQ(caosdb::get_status_description(12412323), "MISSING DESCRIPTION");
+  EXPECT_EQ(caosdb::get_status_description(static_cast<int>(StatusCode::UNKNOWN)),
+            "Unknown error. This is typically a bug (server or client).");
+}
+
 } // namespace caosdb::utility
diff --git a/test/test_value.cpp b/test/test_value.cpp
index 25f8a12909e52fee4e3ed3a6b688b1871a41a32f..241f2bd0ce2a7291184a79cdaa0612f2b99fa204 100644
--- a/test/test_value.cpp
+++ b/test/test_value.cpp
@@ -22,6 +22,7 @@
 
 #include "caosdb/value.h"                   // for Value
 #include "caosdb/entity/v1alpha1/main.pb.h" // for AtomicDataType, DataType
+#include "caosdb/protobuf_helper.h"         // for ProtoMessageWrapper
 #include <algorithm>                        // for max
 #include <cmath>                            // for isnan
 #include <gtest/gtest-message.h>            // for Message
@@ -32,6 +33,7 @@
 #include <vector>                           // for vector
 
 namespace caosdb::entity {
+using ProtoValue = caosdb::entity::v1alpha1::Value;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using ProtoParent = caosdb::entity::v1alpha1::Parent;
 using ProtoDataType = caosdb::entity::v1alpha1::DataType;
@@ -174,4 +176,21 @@ TEST(test_value, test_abstract_value) {
   EXPECT_EQ(scalar_value2.GetAsDouble(), 27.5);
 }
 
+TEST(test_value, test_value_to_string) {
+  Value value1;
+  EXPECT_EQ(value1.ToString(), "{}\n");
+
+  ProtoValue proto_value;
+  Value value2(&proto_value);
+  EXPECT_EQ(value2.ToString(), "{}\n");
+
+  Value value3(2.6);
+  EXPECT_EQ(value3.ToString(), "{\n \"scalarValue\": {\n  \"doubleValue\": 2.6\n }\n}\n");
+
+  Value value4(std::vector<bool>{true, false});
+  EXPECT_EQ(value4.ToString(),
+            "{\n \"listValues\": {\n  \"values\": [\n   {\n    \"booleanValue\": true\n   },\n   "
+            "{\n    \"booleanValue\": false\n   }\n  ]\n }\n}\n");
+}
+
 } // namespace caosdb::entity