added support for optionals in unpackProperties

- Added support for optionals
- Added support for pointers
- Removed support for moving data from input container
- Removed support for container types other than std::vector

Tested:
- Unit tests are passing
- Examples which use unpackProperties are working correctly

Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
Change-Id: I3dbd7333237a1373d784187951ad6abe217567d1
diff --git a/example/get-all-properties.cpp b/example/get-all-properties.cpp
index 330e0c1..53d2466 100644
--- a/example/get-all-properties.cpp
+++ b/example/get-all-properties.cpp
@@ -61,26 +61,26 @@
         ++fatalErrors_;
     }
 
-    void logBadProperty(const std::string& badProperty)
+    void logUnpackError(const sdbusplus::UnpackErrorReason reason,
+                        const std::string& property)
     {
-        std::cerr << "BadProperty: " << badProperty << "\n";
+        std::cerr << "UnpackError: " << static_cast<int>(reason) << ", "
+                  << property << "\n";
         ++fatalErrors_;
     }
 
     void logExpectedException(
         const sdbusplus::exception::UnpackPropertyError& error)
     {
-        std::cout << "As expected " << error.what() << " => "
-                  << error.propertyName << " is missing because "
-                  << error.reason << "\n";
+        std::cout << "As expected " << error.what() << "\n";
     }
 
     void asyncGetAllPropertiesStringTypeOnly()
     {
         sdbusplus::asio::getAllProperties(
             bus_, demoServiceName, demoObjectPath, demoInterfaceName,
-            [this](boost::system::error_code ec,
-                   std::vector<std::pair<
+            [this](const boost::system::error_code ec,
+                   const std::vector<std::pair<
                        std::string, std::variant<std::monostate, std::string>>>&
                        properties) -> void {
                 if (ec)
@@ -89,22 +89,25 @@
                     return;
                 }
                 {
-                    std::string greetings;
-                    std::string goodbyes;
-                    std::optional<std::string> badProperty =
-                        sdbusplus::unpackPropertiesNoThrow(
-                            properties, propertyGrettingName, greetings,
-                            propertyGoodbyesName, goodbyes);
+                    const std::string* greetings = nullptr;
+                    const std::string* goodbyes = nullptr;
+                    const bool success = sdbusplus::unpackPropertiesNoThrow(
+                        [this](const sdbusplus::UnpackErrorReason reason,
+                               const std::string& property) {
+                            logUnpackError(reason, property);
+                        },
+                        properties, propertyGrettingName, greetings,
+                        propertyGoodbyesName, goodbyes);
 
-                    if (badProperty)
+                    if (success)
                     {
-                        logBadProperty(*badProperty);
+                        std::cout << "value of greetings: " << *greetings
+                                  << "\n";
+                        std::cout << "value of goodbyes: " << *goodbyes << "\n";
                     }
                     else
                     {
-                        std::cout << "value of greetings: " << greetings
-                                  << "\n";
-                        std::cout << "value of goodbyes: " << goodbyes << "\n";
+                        ++fatalErrors_;
                     }
                 }
 
@@ -129,8 +132,8 @@
     {
         sdbusplus::asio::getAllProperties(
             bus_, demoServiceName, demoObjectPath, demoInterfaceName,
-            [this](boost::system::error_code ec,
-                   std::vector<std::pair<
+            [this](const boost::system::error_code ec,
+                   const std::vector<std::pair<
                        std::string,
                        std::variant<std::monostate, std::string, uint32_t>>>&
                        properties) -> void {
diff --git a/include/sdbusplus/asio/property.hpp b/include/sdbusplus/asio/property.hpp
index 24c31bc..7dbec52 100644
--- a/include/sdbusplus/asio/property.hpp
+++ b/include/sdbusplus/asio/property.hpp
@@ -1,41 +1,61 @@
 #pragma once
 
+#include <boost/type_traits.hpp>
 #include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/utility/type_traits.hpp>
 
 namespace sdbusplus::asio
 {
 
+template <typename VariantType>
+inline void getAllProperties(
+    sdbusplus::asio::connection& bus, const std::string& service,
+    const std::string& path, const std::string& interface,
+    std::function<void(
+        const boost::system::error_code,
+        const std::vector<std::pair<std::string, VariantType>>&)>&& handler)
+{
+    static_assert(std::is_same_v<VariantType, std::decay_t<VariantType>>);
+
+    bus.async_method_call(std::move(handler), service, path,
+                          "org.freedesktop.DBus.Properties", "GetAll",
+                          interface);
+}
+
 template <typename Handler>
 inline void getAllProperties(sdbusplus::asio::connection& bus,
                              const std::string& service,
                              const std::string& path,
                              const std::string& interface, Handler&& handler)
 {
-    bus.async_method_call(std::forward<Handler>(handler), service, path,
-                          "org.freedesktop.DBus.Properties", "GetAll",
-                          interface);
+    using arg1_type =
+        std::tuple_element_t<1, boost::callable_traits::args_t<Handler>>;
+    using arg1_pair_type = std::decay_t<arg1_type>::value_type;
+    using arg1_value_type = arg1_pair_type::second_type;
+    getAllProperties<arg1_value_type>(bus, service, path, interface,
+                                      std::forward<Handler>(handler));
 }
 
-template <typename T>
-inline void
-    getProperty(sdbusplus::asio::connection& bus, const std::string& service,
-                const std::string& path, const std::string& interface,
-                const std::string& propertyName,
-                std::function<void(boost::system::error_code, T)>&& handler)
+template <typename PropertyType>
+inline void getProperty(
+    sdbusplus::asio::connection& bus, const std::string& service,
+    const std::string& path, const std::string& interface,
+    const std::string& propertyName,
+    std::function<void(boost::system::error_code, PropertyType)>&& handler)
 {
-    static_assert(std::is_same_v<T, std::decay_t<T>>);
+    static_assert(std::is_same_v<PropertyType, std::decay_t<PropertyType>>);
 
     bus.async_method_call(
-        [handler =
-             std::move(handler)](boost::system::error_code ec,
-                                 std::variant<std::monostate, T>& ret) mutable {
+        [handler = std::move(handler)](
+            boost::system::error_code ec,
+            std::variant<std::monostate, PropertyType>& ret) mutable {
             if (ec)
             {
                 handler(ec, {});
                 return;
             }
 
-            if (T* value = std::get_if<T>(&ret))
+            if (PropertyType* value = std::get_if<PropertyType>(&ret))
             {
                 handler(ec, std::move(*value));
                 return;
@@ -82,17 +102,18 @@
         propertyName);
 }
 
-template <typename T, typename Handler>
+template <typename PropertyType, typename Handler>
 inline void setProperty(sdbusplus::asio::connection& bus,
                         const std::string& service, const std::string& path,
                         const std::string& interface,
-                        const std::string& propertyName, T&& propertyValue,
-                        Handler&& handler)
+                        const std::string& propertyName,
+                        PropertyType&& propertyValue, Handler&& handler)
 {
-    bus.async_method_call(
-        std::forward<Handler>(handler), service, path,
-        "org.freedesktop.DBus.Properties", "Set", interface, propertyName,
-        std::variant<std::decay_t<T>>(std::forward<T>(propertyValue)));
+    bus.async_method_call(std::forward<Handler>(handler), service, path,
+                          "org.freedesktop.DBus.Properties", "Set", interface,
+                          propertyName,
+                          std::variant<std::decay_t<PropertyType>>(
+                              std::forward<PropertyType>(propertyValue)));
 }
 
 } // namespace sdbusplus::asio
diff --git a/include/sdbusplus/exception.hpp b/include/sdbusplus/exception.hpp
index 516f6f3..1b17f07 100644
--- a/include/sdbusplus/exception.hpp
+++ b/include/sdbusplus/exception.hpp
@@ -10,6 +10,12 @@
 namespace sdbusplus
 {
 
+enum class UnpackErrorReason
+{
+    missingProperty,
+    wrongType
+};
+
 namespace exception
 {
 
@@ -100,11 +106,8 @@
 class UnpackPropertyError final : public internal_exception
 {
   public:
-    UnpackPropertyError(std::string_view propertyName, std::string_view reason);
-
-    static constexpr std::string_view reasonMissingProperty =
-        "Missing property";
-    static constexpr std::string_view reasonTypeNotMatched = "Type not matched";
+    UnpackPropertyError(std::string_view propertyName,
+                        const UnpackErrorReason reason);
 
     static constexpr auto errName =
         "xyz.openbmc_project.sdbusplus.Error.UnpackPropertyError";
@@ -120,7 +123,7 @@
     int get_errno() const noexcept override;
 
     const std::string propertyName;
-    const std::string reason;
+    const UnpackErrorReason reason;
 
   private:
     const std::string errWhatDetailed;
diff --git a/include/sdbusplus/unpack_properties.hpp b/include/sdbusplus/unpack_properties.hpp
index 4c1adfa..ac681aa 100644
--- a/include/sdbusplus/unpack_properties.hpp
+++ b/include/sdbusplus/unpack_properties.hpp
@@ -13,151 +13,151 @@
 
 namespace sdbusplus
 {
-namespace detail
+
+namespace details
 {
 
-template <typename Variant, typename ValueType>
-bool getIf(Variant&& variant, ValueType& outValue) noexcept
+template <typename VariantType>
+inline auto findProperty(
+    const std::vector<std::pair<std::string, VariantType>>& container,
+    const std::string& key) noexcept
 {
-    if (auto value = std::get_if<ValueType>(&variant))
-    {
-        outValue = std::move(*value);
-        return true;
-    }
-
-    return false;
+    return std::find_if(
+        container.begin(), container.end(),
+        [&key](const auto& keyValue) { return keyValue.first == key; });
 }
 
-template <typename Container>
-auto findProperty(Container&& container, const std::string& key) noexcept
+template <typename OnErrorCallback, typename VariantType, typename ValueType>
+inline bool readProperty(
+    const OnErrorCallback& onErrorCallback,
+    const std::vector<std::pair<std::string, VariantType>>& container,
+    const std::string& expectedKey,
+    ValueType&
+        outValue) noexcept(noexcept(onErrorCallback(sdbusplus::
+                                                        UnpackErrorReason{},
+                                                    std::string{})))
 {
-    if constexpr (utility::has_member_find_v<Container>)
-    {
-        return container.find(key);
-    }
-    else
-    {
-        return std::find_if(
-            std::begin(container), std::end(container),
-            [&key](const auto& keyValue) { return keyValue.first == key; });
-    }
-}
+    auto it = findProperty(container, expectedKey);
 
-template <typename Container>
-bool containsProperty(Container&& container, const std::string& key) noexcept
-{
-    if constexpr (utility::has_member_contains_v<Container>)
+    if (it != container.end())
     {
-        return container.contains(key);
-    }
-    else
-    {
-        return findProperty(std::forward<Container>(container), key) !=
-               std::end(container);
-    }
-}
-
-template <size_t Index, typename Container, size_t N, typename ValueType,
-          typename... Args>
-void readProperties(Container&& container, std::bitset<N>& assigned,
-                    const std::string& expectedKey, ValueType& outValue,
-                    Args&&... args) noexcept
-{
-    static_assert(Index < N);
-
-    auto it = findProperty(std::forward<Container>(container), expectedKey);
-
-    if (it != std::end(container))
-    {
-        if (getIf(it->second, outValue))
+        if constexpr (std::is_pointer_v<ValueType>)
         {
-            assigned.set(Index);
-        }
-    }
-
-    if constexpr (sizeof...(Args) > 0)
-    {
-        readProperties<Index + 1>(std::forward<Container>(container), assigned,
-                                  std::forward<Args>(args)...);
-    }
-}
-
-template <size_t Index, size_t N, typename ValueType, typename... Args>
-std::string findMissingProperty(std::bitset<N>& assigned,
-                                const std::string& key, ValueType&,
-                                Args&&... args) noexcept
-{
-    static_assert(Index < N);
-
-    if (!assigned.test(Index))
-    {
-        return key;
-    }
-
-    if constexpr (sizeof...(Args) > 0)
-    {
-        return findMissingProperty<Index + 1>(assigned,
-                                              std::forward<Args>(args)...);
-    }
-
-    return {};
-}
-
-template <bool ReturnBadProperty, typename Container, typename... Args>
-auto unpackPropertiesCommon(Container&& input,
-                            Args&&... args) noexcept(ReturnBadProperty)
-{
-    static_assert(sizeof...(Args) % 2 == 0);
-
-    auto assigned = std::bitset<sizeof...(Args) / 2>();
-
-    detail::readProperties<0>(input, assigned, std::forward<Args>(args)...);
-
-    if (!assigned.all())
-    {
-        auto missingProperty = detail::findMissingProperty<0>(
-            assigned, std::forward<Args>(args)...);
-
-        if constexpr (ReturnBadProperty)
-        {
-            return std::optional{missingProperty};
-        }
-        else
-        {
-            if (detail::containsProperty(std::forward<Container>(input),
-                                         missingProperty))
+            if (const auto* value = std::get_if<
+                    std::remove_cv_t<std::remove_pointer_t<ValueType>>>(
+                    &it->second))
             {
-                throw exception::UnpackPropertyError(
-                    missingProperty,
-                    exception::UnpackPropertyError::reasonTypeNotMatched);
+                outValue = value;
             }
             else
             {
-                throw exception::UnpackPropertyError(
-                    missingProperty,
-                    exception::UnpackPropertyError::reasonMissingProperty);
+                onErrorCallback(UnpackErrorReason::wrongType, expectedKey);
+                return false;
+            }
+        }
+        else if constexpr (utility::is_optional_v<ValueType>)
+        {
+            using InnerType = typename ValueType::value_type;
+            static_assert(!std::is_pointer_v<InnerType>,
+                          "std::optional<T*> is not supported");
+            if (const auto value = std::get_if<InnerType>(&it->second))
+
+            {
+                outValue = *value;
+            }
+            else
+            {
+                onErrorCallback(UnpackErrorReason::wrongType, expectedKey);
+                return false;
+            }
+        }
+        else
+        {
+            if (const auto value = std::get_if<ValueType>(&it->second))
+            {
+                outValue = *value;
+            }
+            else
+            {
+                onErrorCallback(UnpackErrorReason::wrongType, expectedKey);
+                return false;
             }
         }
     }
-    return std::conditional_t<ReturnBadProperty, std::optional<std::string>,
-                              void>();
+    else if constexpr (!utility::is_optional_v<ValueType> &&
+                       !std::is_pointer_v<ValueType>)
+    {
+        onErrorCallback(UnpackErrorReason::missingProperty, expectedKey);
+        return false;
+    }
+
+    return true;
 }
 
-} // namespace detail
-
-template <typename Container, typename... Args>
-void unpackProperties(Container&& input, Args&&... args)
+template <typename OnErrorCallback, typename VariantType, typename ValueType,
+          typename... Args>
+inline bool readProperties(
+    OnErrorCallback&& onErrorCallback,
+    const std::vector<std::pair<std::string, VariantType>>& container,
+    const std::string& expectedKey, ValueType& outValue,
+    Args&&... args) noexcept(noexcept(onErrorCallback(sdbusplus::
+                                                          UnpackErrorReason{},
+                                                      std::string{})))
 {
-    detail::unpackPropertiesCommon<false, Container, Args...>(
-        std::forward<Container>(input), std::forward<Args>(args)...);
+    if (!readProperty(onErrorCallback, container, expectedKey, outValue))
+    {
+        return false;
+    }
+
+    if constexpr (sizeof...(Args) > 0)
+    {
+        return readProperties(std::forward<OnErrorCallback>(onErrorCallback),
+                              container, std::forward<Args>(args)...);
+    }
+
+    return true;
 }
 
-template <typename Container, typename... Args>
-std::optional<std::string> unpackPropertiesNoThrow(Container&& input,
-                                                   Args&&... args) noexcept
+template <typename OnErrorCallback, typename VariantType, typename... Args>
+inline auto unpackPropertiesCommon(
+    OnErrorCallback&& onErrorCallback,
+    const std::vector<std::pair<std::string, VariantType>>& input,
+    Args&&... args) noexcept(noexcept(onErrorCallback(sdbusplus::
+                                                          UnpackErrorReason{},
+                                                      std::string{})))
 {
-    return detail::unpackPropertiesCommon<true, Container, Args...>(
-        std::forward<Container>(input), std::forward<Args>(args)...);
+    static_assert(
+        sizeof...(Args) % 2 == 0,
+        "Expected number of arguments to be even, but got odd number instead");
+
+    return details::readProperties(
+        std::forward<OnErrorCallback>(onErrorCallback), input,
+        std::forward<Args>(args)...);
+}
+
+} // namespace details
+
+template <typename VariantType, typename... Args>
+inline void unpackProperties(
+    const std::vector<std::pair<std::string, VariantType>>& input,
+    Args&&... args)
+{
+    details::unpackPropertiesCommon(
+        [](const UnpackErrorReason reason, const std::string& property) {
+            throw exception::UnpackPropertyError(property, reason);
+        },
+        input, std::forward<Args>(args)...);
+}
+
+template <typename OnErrorCallback, typename VariantType, typename... Args>
+inline bool unpackPropertiesNoThrow(
+    OnErrorCallback&& onErrorCallback,
+    const std::vector<std::pair<std::string, VariantType>>& input,
+    Args&&... args) noexcept
+{
+    return details::unpackPropertiesCommon(
+        std::forward<OnErrorCallback>(onErrorCallback), input,
+        std::forward<Args>(args)...);
 }
 
 } // namespace sdbusplus
diff --git a/include/sdbusplus/utility/type_traits.hpp b/include/sdbusplus/utility/type_traits.hpp
index 2b0ddd0..b91fa20 100644
--- a/include/sdbusplus/utility/type_traits.hpp
+++ b/include/sdbusplus/utility/type_traits.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include <cstddef>
+#include <optional>
 #include <tuple>
 #include <type_traits>
 
@@ -145,6 +147,29 @@
 template <typename T>
 constexpr bool has_member_contains_v = has_member_contains<T>::value;
 
+template <typename T>
+struct is_optional : public std::false_type
+{};
+
+template <typename T>
+struct is_optional<std::optional<T>> : public std::true_type
+{};
+
+template <typename T>
+struct is_optional<const std::optional<T>> : public std::true_type
+{};
+
+template <typename T>
+struct is_optional<std::optional<T>&> : public std::true_type
+{};
+
+template <typename T>
+struct is_optional<const std::optional<T>&> : public std::true_type
+{};
+
+template <typename T>
+constexpr bool is_optional_v = is_optional<T>::value;
+
 } // namespace utility
 
 } // namespace sdbusplus
diff --git a/src/exception.cpp b/src/exception.cpp
index 4a04dea..05edf92 100644
--- a/src/exception.cpp
+++ b/src/exception.cpp
@@ -139,12 +139,24 @@
     return EINVAL;
 }
 
+std::string unpackErrorReasonToString(const UnpackErrorReason reason)
+{
+    switch (reason)
+    {
+        case UnpackErrorReason::missingProperty:
+            return "Missing property";
+        case UnpackErrorReason::wrongType:
+            return "Type not matched";
+    }
+    return "Unknown";
+}
+
 UnpackPropertyError::UnpackPropertyError(std::string_view propertyNameIn,
-                                         std::string_view reasonIn) :
+                                         const UnpackErrorReason reasonIn) :
     propertyName(propertyNameIn),
     reason(reasonIn),
     errWhatDetailed(std::string(errWhat) + " PropertyName: '" + propertyName +
-                    "', Reason: '" + reason + "'.")
+                    "', Reason: '" + unpackErrorReasonToString(reason) + "'.")
 {}
 
 const char* UnpackPropertyError::name() const noexcept
diff --git a/test/unpack_properties.cpp b/test/unpack_properties.cpp
index cb7b122..656dc41 100644
--- a/test/unpack_properties.cpp
+++ b/test/unpack_properties.cpp
@@ -18,10 +18,23 @@
 
 struct NonThrowingUnpack
 {
-    template <typename... Args>
-    std::optional<std::string> operator()(Args&&... args) const
+    struct UnpackError
     {
-        return unpackPropertiesNoThrow(std::forward<Args>(args)...);
+        sdbusplus::UnpackErrorReason reason;
+        std::string property;
+    };
+
+    template <typename... Args>
+    std::optional<UnpackError> operator()(Args&&... args) const
+    {
+        std::optional<UnpackError> error;
+        unpackPropertiesNoThrow(
+            [&error](const sdbusplus::UnpackErrorReason reason,
+                     const std::string& property) {
+                error.emplace(reason, property);
+            },
+            std::forward<Args>(args)...);
+        return error;
     }
 };
 
@@ -36,14 +49,8 @@
 using ContainerTypes = testing::Types<
     TestingTypes<NonThrowingUnpack,
                  std::vector<std::pair<std::string, VariantType>>>,
-    TestingTypes<NonThrowingUnpack,
-                 boost::container::flat_map<std::string, VariantType>>,
-    TestingTypes<NonThrowingUnpack, std::map<std::string, VariantType>>,
     TestingTypes<ThrowingUnpack,
-                 std::vector<std::pair<std::string, VariantType>>>,
-    TestingTypes<ThrowingUnpack,
-                 boost::container::flat_map<std::string, VariantType>>,
-    TestingTypes<ThrowingUnpack, std::map<std::string, VariantType>>>;
+                 std::vector<std::pair<std::string, VariantType>>>>;
 
 template <typename Exception, typename F>
 std::optional<Exception> captureException(F&& code)
@@ -96,7 +103,7 @@
 }
 
 TYPED_TEST(UnpackPropertiesTest,
-           unpackChangesOriginalDataWhenPassedAsNonConstReference)
+           unpackDoesntChangeOriginalDataWhenPassedAsNonConstReference)
 {
     using namespace testing;
 
@@ -106,75 +113,52 @@
     EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val2));
 
     ASSERT_THAT(val1, Eq("string"));
-    ASSERT_THAT(val2, Not(Eq("string")));
-}
-
-TYPED_TEST(UnpackPropertiesTest,
-           unpackDoesntChangeOriginalDataWhenPassesAsConstReference)
-{
-    using namespace testing;
-
-    std::string val1, val2;
-
-    EXPECT_FALSE(this->unpackPropertiesCall(Const(this->data), "Key-1", val1));
-    EXPECT_FALSE(this->unpackPropertiesCall(Const(this->data), "Key-1", val2));
-
-    ASSERT_THAT(val1, Eq("string"));
     ASSERT_THAT(val2, Eq("string"));
 }
 
-TYPED_TEST(UnpackPropertiesTest,
-           returnsUndefinedValueForDuplicatedKeysWhenDataIsNonConstReference)
+TYPED_TEST(UnpackPropertiesTest, doesntReportMissingPropertyForOptional)
 {
     using namespace testing;
     using namespace std::string_literals;
 
-    std::string val1;
-    float val2 = 0.f;
-    double val3 = 0.;
-    std::string val4;
+    std::optional<std::string> val1;
+    std::optional<std::string> val4;
 
-    EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2",
-                                            val2, "Key-3", val3, "Key-1",
-                                            val4));
+    EXPECT_FALSE(
+        this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-4", val4));
 
     ASSERT_THAT(val1, Eq("string"));
-    ASSERT_THAT(val2, FloatEq(42.f));
-    ASSERT_THAT(val3, DoubleEq(15.));
-    ASSERT_THAT(val4, Not(Eq("string")));
+    ASSERT_THAT(val4, Eq(std::nullopt));
 }
 
-TYPED_TEST(UnpackPropertiesTest,
-           returnsValueForDuplicatedKeysWhenDataIsConstReference)
+TYPED_TEST(UnpackPropertiesTest, setPresentPointersOnSuccess)
 {
     using namespace testing;
     using namespace std::string_literals;
 
-    std::string val1;
-    float val2 = 0.f;
-    double val3 = 0.;
-    std::string val4;
+    const std::string* val1 = nullptr;
+    const float* val2 = nullptr;
+    const double* val3 = nullptr;
+    const std::string* val4 = nullptr;
 
-    EXPECT_FALSE(this->unpackPropertiesCall(Const(this->data), "Key-1", val1,
-                                            "Key-2", val2, "Key-3", val3,
-                                            "Key-1", val4));
+    EXPECT_FALSE(this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2",
+                                            val2, "Key-3", val3, "Key-4",
+                                            val4));
 
-    ASSERT_THAT(val1, Eq("string"));
-    ASSERT_THAT(val2, FloatEq(42.f));
-    ASSERT_THAT(val3, DoubleEq(15.));
-    ASSERT_THAT(val4, Eq("string"));
+    ASSERT_TRUE(val1 && val2 && val3);
+    ASSERT_TRUE(!val4);
+
+    ASSERT_THAT(*val1, Eq("string"));
+    ASSERT_THAT(*val2, FloatEq(42.f));
+    ASSERT_THAT(*val3, DoubleEq(15.));
 }
 
 template <typename Params>
 struct UnpackPropertiesThrowingTest : public UnpackPropertiesTest<Params>
 {};
 
-using ContainerTypesThrowing = testing::Types<
-    TestingTypes<ThrowingUnpack,
-                 std::vector<std::pair<std::string, VariantType>>>,
-    TestingTypes<ThrowingUnpack,
-                 boost::container::flat_map<std::string, VariantType>>,
-    TestingTypes<ThrowingUnpack, std::map<std::string, VariantType>>>;
+using ContainerTypesThrowing = testing::Types<TestingTypes<
+    ThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>;
 
 TYPED_TEST_SUITE(UnpackPropertiesThrowingTest, ContainerTypesThrowing);
 
@@ -192,8 +176,7 @@
     });
 
     ASSERT_TRUE(error);
-    ASSERT_THAT(error->reason,
-                Eq(exception::UnpackPropertyError::reasonMissingProperty));
+    ASSERT_THAT(error->reason, Eq(UnpackErrorReason::missingProperty));
     ASSERT_THAT(error->propertyName, Eq("Key-4"));
 }
 
@@ -211,8 +194,23 @@
     });
 
     ASSERT_TRUE(error);
-    ASSERT_THAT(error->reason,
-                Eq(exception::UnpackPropertyError::reasonTypeNotMatched));
+    ASSERT_THAT(error->reason, Eq(UnpackErrorReason::wrongType));
+    ASSERT_THAT(error->propertyName, Eq("Key-2"));
+}
+
+TYPED_TEST(UnpackPropertiesThrowingTest, throwsErrorWhenOptionalTypeDoesntMatch)
+{
+    using namespace testing;
+
+    std::optional<std::string> val1;
+    std::optional<std::string> val2;
+
+    auto error = captureException<exception::UnpackPropertyError>([&] {
+        this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2);
+    });
+
+    ASSERT_TRUE(error);
+    ASSERT_THAT(error->reason, Eq(UnpackErrorReason::wrongType));
     ASSERT_THAT(error->propertyName, Eq("Key-2"));
 }
 
@@ -220,12 +218,8 @@
 struct UnpackPropertiesNonThrowingTest : public UnpackPropertiesTest<Params>
 {};
 
-using ContainerTypesNonThrowing = testing::Types<
-    TestingTypes<NonThrowingUnpack,
-                 std::vector<std::pair<std::string, VariantType>>>,
-    TestingTypes<NonThrowingUnpack,
-                 boost::container::flat_map<std::string, VariantType>>,
-    TestingTypes<NonThrowingUnpack, std::map<std::string, VariantType>>>;
+using ContainerTypesNonThrowing = testing::Types<TestingTypes<
+    NonThrowingUnpack, std::vector<std::pair<std::string, VariantType>>>>;
 
 TYPED_TEST_SUITE(UnpackPropertiesNonThrowingTest, ContainerTypesNonThrowing);
 
@@ -241,7 +235,8 @@
                                                   "Key-4", val2, "Key-3", val3);
 
     ASSERT_TRUE(badProperty);
-    ASSERT_THAT(*badProperty, Eq("Key-4"));
+    EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::missingProperty));
+    EXPECT_THAT(badProperty->property, Eq("Key-4"));
 }
 
 TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenTypeDoesntMatch)
@@ -256,7 +251,23 @@
                                                   "Key-2", val2, "Key-3", val3);
 
     ASSERT_TRUE(badProperty);
-    ASSERT_THAT(*badProperty, Eq("Key-2"));
+    EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::wrongType));
+    EXPECT_THAT(badProperty->property, Eq("Key-2"));
+}
+
+TYPED_TEST(UnpackPropertiesNonThrowingTest, ErrorWhenOptionalTypeDoesntMatch)
+{
+    using namespace testing;
+
+    std::optional<std::string> val1;
+    std::optional<std::string> val2;
+
+    auto badProperty =
+        this->unpackPropertiesCall(this->data, "Key-1", val1, "Key-2", val2);
+
+    ASSERT_TRUE(badProperty);
+    EXPECT_THAT(badProperty->reason, Eq(UnpackErrorReason::wrongType));
+    EXPECT_THAT(badProperty->property, Eq("Key-2"));
 }
 
 template <typename Params>
diff --git a/test/utility/type_traits.cpp b/test/utility/type_traits.cpp
index 8526f29..e5197d2 100644
--- a/test/utility/type_traits.cpp
+++ b/test/utility/type_traits.cpp
@@ -84,6 +84,20 @@
     ASSERT_THAT(has_member_contains_v<Bar>, Eq(false));
 }
 
+TEST(TypeTraits, IsOptional)
+{
+    using sdbusplus::utility::is_optional;
+    using sdbusplus::utility::is_optional_v;
+
+    ASSERT_TRUE(is_optional<std::optional<int>>::value);
+    ASSERT_TRUE(is_optional<std::optional<int>&>::value);
+    ASSERT_FALSE(is_optional<int>::value);
+
+    ASSERT_TRUE(is_optional_v<std::optional<int>>);
+    ASSERT_TRUE(is_optional_v<std::optional<int>&>);
+    ASSERT_FALSE(is_optional_v<int>);
+}
+
 // Tests for dedup_variant.
 static_assert(std::is_same_v<std::variant<size_t>,
                              sdbusplus::utility::dedup_variant_t<size_t>>);