Persistency: store special double values
Json library handles infinity and NaN as null. During deserialization,
we expect a double and those files were marked as invalid and removed.
To prevent this behavior and preserve all double values, LabeledTuple
'to_json' and 'from_json' functions were improved to accommodate those
special values by using string literals.
Testing done:
- UTs added for double<=>json conversion.
- UTs passing.
Signed-off-by: Szymon Dompke <szymon.dompke@intel.com>
Change-Id: I9193df29cce1db28cda3c895d117d9f3bfca2c24
diff --git a/src/utils/labeled_tuple.hpp b/src/utils/labeled_tuple.hpp
index e6203dd..c4120c9 100644
--- a/src/utils/labeled_tuple.hpp
+++ b/src/utils/labeled_tuple.hpp
@@ -3,9 +3,19 @@
#include <nlohmann/json.hpp>
#include <sdbusplus/message/types.hpp>
+#include <cmath>
+#include <limits>
+
namespace utils
{
+namespace numeric_literals
+{
+constexpr std::string_view NaN = "NaN";
+constexpr std::string_view infinity = "inf";
+constexpr std::string_view infinity_negative = "-inf";
+} // namespace numeric_literals
+
inline void from_json(const nlohmann::json& j,
sdbusplus::message::object_path& o)
{
@@ -22,6 +32,54 @@
}
}
+inline void to_json(nlohmann::json& j, const double& val)
+{
+ if (std::isnan(val))
+ {
+ j = numeric_literals::NaN;
+ }
+ else if (val == std::numeric_limits<double>::infinity())
+ {
+ j = numeric_literals::infinity;
+ }
+ else if (val == -std::numeric_limits<double>::infinity())
+ {
+ j = numeric_literals::infinity_negative;
+ }
+ else
+ {
+ j = val;
+ }
+}
+
+inline void from_json(const nlohmann::json& j, double& val)
+{
+ if (j.is_number())
+ {
+ val = j.get<double>();
+ }
+ else
+ {
+ auto str_val = j.get<std::string>();
+ if (str_val == numeric_literals::NaN)
+ {
+ val = std::numeric_limits<double>::quiet_NaN();
+ }
+ else if (str_val == numeric_literals::infinity)
+ {
+ val = std::numeric_limits<double>::infinity();
+ }
+ else if (str_val == numeric_literals::infinity_negative)
+ {
+ val = -std::numeric_limits<double>::infinity();
+ }
+ else
+ {
+ throw std::invalid_argument("Unknown numeric literal");
+ }
+ }
+}
+
namespace detail
{
@@ -45,6 +103,38 @@
template <class T>
constexpr bool has_utils_from_json_v = has_utils_from_json<T>::value;
+template <class T>
+struct has_utils_to_json
+{
+ template <class U>
+ static U& ref();
+
+ template <class U>
+ static std::true_type
+ check(decltype(utils::to_json(ref<nlohmann::json>(), ref<const U>()))*);
+
+ template <class>
+ static std::false_type check(...);
+
+ static constexpr bool value =
+ decltype(check<std::decay_t<T>>(nullptr))::value;
+};
+
+template <class T>
+constexpr bool has_utils_to_json_v = has_utils_to_json<T>::value;
+
+bool eq(const auto& a, const auto& b)
+{
+ if constexpr (std::is_same<std::decay_t<decltype(a)>, double>())
+ {
+ if (std::isnan(a))
+ {
+ return std::isnan(b);
+ }
+ }
+ return a == b;
+}
+
} // namespace detail
template <class, class...>
@@ -117,7 +207,15 @@
bool operator==(const LabeledTuple& other) const
{
- return value == other.value;
+ return std::apply(
+ [&](auto&&... x) {
+ return std::apply(
+ [&](auto&&... y) {
+ return (true && ... && detail::eq(x, y));
+ },
+ value);
+ },
+ other.value);
}
bool operator<(const LabeledTuple& other) const
@@ -136,7 +234,16 @@
void to_json_item(nlohmann::json& j) const
{
using Label = std::tuple_element_t<Idx, std::tuple<Labels...>>;
- j[Label::str()] = std::get<Idx>(value);
+ using T = std::tuple_element_t<Idx, tuple_type>;
+ nlohmann::json& item = j[Label::str()];
+ if constexpr (detail::has_utils_to_json_v<T>)
+ {
+ utils::to_json(item, std::get<Idx>(value));
+ }
+ else
+ {
+ item = std::get<Idx>(value);
+ }
}
template <size_t... Idx>