Review fixes for 'Created metric class'

- Changed metric to_json to dumpConfiguration returning LabeledTuple
- LabeledTuple can be created and assigned directly to json
- LabeledTuple can be readed from json using json.get<LabeledTuple>
- Added PrintTo for LabeledMetricParams, LabeledSensorParams
- Added helper method expectMake to ReportFactoryMock
- sensorPaths are serialized to tuple<service, path> instead of single
  field with service and path separated via ':'
- Changed configuration version from 1 to 2

Change-Id: I7c45fb584687172f88fd549a93329264793b0b8e
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/src/interfaces/metric.hpp b/src/interfaces/metric.hpp
index d63c979..5a960a4 100644
--- a/src/interfaces/metric.hpp
+++ b/src/interfaces/metric.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include "interfaces/types.hpp"
 #include "metric_value.hpp"
 
 #include <nlohmann/json.hpp>
@@ -16,7 +17,7 @@
 
     virtual void initialize() = 0;
     virtual const std::vector<MetricValue>& getReadings() const = 0;
-    virtual nlohmann::json to_json() const = 0;
+    virtual LabeledMetricParameters dumpConfiguration() const = 0;
 };
 
 } // namespace interfaces
diff --git a/src/interfaces/report_factory.hpp b/src/interfaces/report_factory.hpp
index 056c9e0..237ae7b 100644
--- a/src/interfaces/report_factory.hpp
+++ b/src/interfaces/report_factory.hpp
@@ -20,11 +20,17 @@
     virtual ~ReportFactory() = default;
 
     virtual std::unique_ptr<interfaces::Report> make(
-        std::optional<std::reference_wrapper<boost::asio::yield_context>> yield,
+        boost::asio::yield_context& yield, const std::string& name,
+        const std::string& reportingType, bool emitsReadingsSignal,
+        bool logToMetricReportsCollection, std::chrono::milliseconds period,
+        const ReadingParameters& metricParams, ReportManager& reportManager,
+        JsonStorage& reportStorage) const = 0;
+    virtual std::unique_ptr<interfaces::Report> make(
         const std::string& name, const std::string& reportingType,
         bool emitsReadingsSignal, bool logToMetricReportsCollection,
         std::chrono::milliseconds period, const ReadingParameters& metricParams,
-        ReportManager& reportManager, JsonStorage& reportStorage) const = 0;
+        ReportManager& reportManager, JsonStorage& reportStorage,
+        std::vector<LabeledMetricParameters> labeledMetricParams) const = 0;
 };
 
 } // namespace interfaces
diff --git a/src/interfaces/types.hpp b/src/interfaces/types.hpp
index 1eca51a..de97353 100644
--- a/src/interfaces/types.hpp
+++ b/src/interfaces/types.hpp
@@ -13,8 +13,13 @@
     std::vector<std::tuple<std::vector<sdbusplus::message::object_path>,
                            std::string, std::string, std::string>>;
 
-using LabeledReadingParameter =
-    utils::LabeledTuple<ReadingParameters::value_type,
+using LabeledSensorParameters =
+    utils::LabeledTuple<std::tuple<std::string, std::string>,
+                        utils::tstring::Service, utils::tstring::Path>;
+
+using LabeledMetricParameters =
+    utils::LabeledTuple<std::tuple<std::vector<LabeledSensorParameters>,
+                                   std::string, std::string, std::string>,
                         utils::tstring::SensorPaths,
                         utils::tstring::OperationType, utils::tstring::Id,
                         utils::tstring::MetricMetadata>;
diff --git a/src/metric.cpp b/src/metric.cpp
index 8d9f19b..b12f3a6 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -52,13 +52,11 @@
     return readings.at(index);
 }
 
-nlohmann::json Metric::to_json() const
+LabeledMetricParameters Metric::dumpConfiguration() const
 {
-    auto sensorPaths = utils::transform(
-        sensors, [](const auto& sensor) -> sdbusplus::message::object_path {
-            return sdbusplus::message::object_path(sensor->id().service + ":" +
-                                                   sensor->id().path);
-        });
-    return LabeledReadingParameter::to_json(ReadingParameters::value_type(
-        std::move(sensorPaths), operationType, id, metadata));
+    auto sensorPaths = utils::transform(sensors, [](const auto& sensor) {
+        return LabeledSensorParameters(sensor->id().service, sensor->id().path);
+    });
+    return LabeledMetricParameters(std::move(sensorPaths), operationType, id,
+                                   metadata);
 }
diff --git a/src/metric.hpp b/src/metric.hpp
index 6e27446..39f525c 100644
--- a/src/metric.hpp
+++ b/src/metric.hpp
@@ -17,7 +17,7 @@
     const std::vector<MetricValue>& getReadings() const override;
     void sensorUpdated(interfaces::Sensor&, uint64_t) override;
     void sensorUpdated(interfaces::Sensor&, uint64_t, double value) override;
-    nlohmann::json to_json() const override;
+    LabeledMetricParameters dumpConfiguration() const override;
 
   private:
     MetricValue& findMetric(interfaces::Sensor&);
diff --git a/src/report.cpp b/src/report.cpp
index 4d91787..dc8f37b 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -28,11 +28,6 @@
     fileName(std::to_string(std::hash<std::string>{}(name))),
     reportStorage(reportStorageIn)
 {
-    for (auto& metric : this->metrics)
-    {
-        metric->initialize();
-    }
-
     deleteIface = objServer->add_unique_interface(
         path, deleteIfaceName, [this, &ioc, &reportManager](auto& dbusIface) {
             dbusIface.register_method("Delete", [this, &ioc, &reportManager] {
@@ -121,6 +116,11 @@
     {
         scheduleTimer(interval);
     }
+
+    for (auto& metric : this->metrics)
+    {
+        metric->initialize();
+    }
 }
 
 void Report::timerProc(boost::system::error_code ec, Report& self)
@@ -179,8 +179,10 @@
         data["EmitsReadingsUpdate"] = emitsReadingsUpdate;
         data["LogToMetricReportsCollection"] = logToMetricReportsCollection;
         data["Interval"] = interval.count();
-        data["ReadingParameters"] = utils::transform(
-            metrics, [](const auto& metric) { return metric->to_json(); });
+        data["ReadingParameters"] =
+            utils::transform(metrics, [](const auto& metric) {
+                return metric->dumpConfiguration();
+            });
 
         reportStorage.store(fileName, data);
     }
diff --git a/src/report.hpp b/src/report.hpp
index b227392..c58d38b 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -75,5 +75,5 @@
         "/xyz/openbmc_project/Telemetry/Reports/";
     static constexpr const char* deleteIfaceName =
         "xyz.openbmc_project.Object.Delete";
-    static constexpr size_t reportVersion = 1;
+    static constexpr size_t reportVersion = 2;
 };
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
index e4689aa..b8b5518 100644
--- a/src/report_factory.cpp
+++ b/src/report_factory.cpp
@@ -13,31 +13,35 @@
 {}
 
 std::unique_ptr<interfaces::Report> ReportFactory::make(
-    std::optional<std::reference_wrapper<boost::asio::yield_context>> yield,
+    boost::asio::yield_context& yield, const std::string& name,
+    const std::string& reportingType, bool emitsReadingsSignal,
+    bool logToMetricReportsCollection, std::chrono::milliseconds period,
+    const ReadingParameters& metricParams,
+    interfaces::ReportManager& reportManager,
+    interfaces::JsonStorage& reportStorage) const
+{
+    return make(name, reportingType, emitsReadingsSignal,
+                logToMetricReportsCollection, period, metricParams,
+                reportManager, reportStorage,
+                convertMetricParams(yield, metricParams));
+}
+
+std::unique_ptr<interfaces::Report> ReportFactory::make(
     const std::string& name, const std::string& reportingType,
     bool emitsReadingsSignal, bool logToMetricReportsCollection,
     std::chrono::milliseconds period, const ReadingParameters& metricParams,
     interfaces::ReportManager& reportManager,
-    interfaces::JsonStorage& reportStorage) const
+    interfaces::JsonStorage& reportStorage,
+    std::vector<LabeledMetricParameters> labeledMetricParams) const
 {
-    std::optional<std::vector<ReportFactory::SensorTree>> sensorTree;
-
-    std::vector<std::shared_ptr<interfaces::Metric>> metrics;
-    metrics.reserve(metricParams.size());
-
-    for (const auto& [sensorPaths, op, id, metadata] : metricParams)
-    {
-        if (!sensorTree && yield && sensorPaths.size() > 0)
-        {
-            sensorTree = getSensorTree(*yield);
-        }
-
-        std::vector<std::shared_ptr<interfaces::Sensor>> sensors =
-            getSensors(sensorTree, sensorPaths);
-
-        metrics.emplace_back(
-            std::make_shared<Metric>(std::move(sensors), op, id, metadata));
-    }
+    std::vector<std::shared_ptr<interfaces::Metric>> metrics = utils::transform(
+        labeledMetricParams,
+        [this](const LabeledMetricParameters& param)
+            -> std::shared_ptr<interfaces::Metric> {
+            return std::make_shared<Metric>(
+                getSensors(param.at_index<0>()), param.at_index<1>(),
+                param.at_index<2>(), param.at_index<3>());
+        });
 
     return std::make_unique<Report>(
         bus->get_io_context(), objServer, name, reportingType,
@@ -46,58 +50,57 @@
 }
 
 std::vector<std::shared_ptr<interfaces::Sensor>> ReportFactory::getSensors(
-    const std::optional<std::vector<ReportFactory::SensorTree>>& tree,
-    const std::vector<sdbusplus::message::object_path>& sensorPaths) const
+    const std::vector<LabeledSensorParameters>& sensorPaths) const
 {
-    if (tree)
-    {
-        std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
+    return utils::transform(sensorPaths,
+                            [this](const LabeledSensorParameters& param)
+                                -> std::shared_ptr<interfaces::Sensor> {
+                                using namespace utils::tstring;
 
-        for (const auto& [sensor, ifacesMap] : *tree)
-        {
-            auto it = std::find(sensorPaths.begin(), sensorPaths.end(), sensor);
-            if (it != sensorPaths.end())
-            {
-                for (const auto& [service, ifaces] : ifacesMap)
-                {
-                    sensors.emplace_back(sensorCache.makeSensor<Sensor>(
-                        service, sensor, bus->get_io_context(), bus));
-                }
-            }
-        }
-
-        return sensors;
-    }
-    else
-    {
-        return utils::transform(
-            sensorPaths,
-            [this](const std::string& sensor)
-                -> std::shared_ptr<interfaces::Sensor> {
-                std::string::size_type pos = sensor.find_first_of(":");
-                auto service = sensor.substr(0, pos);
-                auto path = sensor.substr(pos + 1);
-                return sensorCache.makeSensor<Sensor>(
-                    service, path, bus->get_io_context(), bus);
-            });
-    }
+                                return sensorCache.makeSensor<Sensor>(
+                                    param.at_label<Service>(),
+                                    param.at_label<Path>(),
+                                    bus->get_io_context(), bus);
+                            });
 }
 
-std::vector<ReportFactory::SensorTree>
-    ReportFactory::getSensorTree(boost::asio::yield_context& yield) const
+std::vector<LabeledMetricParameters> ReportFactory::convertMetricParams(
+    boost::asio::yield_context& yield,
+    const ReadingParameters& metricParams) const
 {
     std::array<const char*, 1> interfaces = {
         "xyz.openbmc_project.Sensor.Value"};
     boost::system::error_code ec;
 
-    auto result = bus->yield_method_call<std::vector<SensorTree>>(
+    auto tree = bus->yield_method_call<std::vector<SensorTree>>(
         yield, ec, "xyz.openbmc_project.ObjectMapper",
         "/xyz/openbmc_project/object_mapper",
         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
         "/xyz/openbmc_project/sensors", 2, interfaces);
     if (ec)
     {
-        throw std::runtime_error("failed");
+        throw std::runtime_error("Failed to query ObjectMapper!");
     }
-    return result;
+
+    return utils::transform(metricParams, [&tree](const auto& item) {
+        std::vector<LabeledSensorParameters> sensors;
+
+        for (const auto& sensorPath : std::get<0>(item))
+        {
+            auto it = std::find_if(
+                tree.begin(), tree.end(),
+                [&sensorPath](const auto& v) { return v.first == sensorPath; });
+
+            if (it != tree.end())
+            {
+                for (const auto& [service, ifaces] : it->second)
+                {
+                    sensors.emplace_back(service, sensorPath);
+                }
+            }
+        }
+
+        return LabeledMetricParameters(std::move(sensors), std::get<1>(item),
+                                       std::get<2>(item), std::get<3>(item));
+    });
 }
diff --git a/src/report_factory.hpp b/src/report_factory.hpp
index 0346441..de2bc02 100644
--- a/src/report_factory.hpp
+++ b/src/report_factory.hpp
@@ -14,13 +14,23 @@
         std::shared_ptr<sdbusplus::asio::connection> bus,
         const std::shared_ptr<sdbusplus::asio::object_server>& objServer);
 
-    std::unique_ptr<interfaces::Report> make(
-        std::optional<std::reference_wrapper<boost::asio::yield_context>> yield,
-        const std::string& name, const std::string& reportingType,
-        bool emitsReadingsSignal, bool logToMetricReportsCollection,
-        std::chrono::milliseconds period, const ReadingParameters& metricParams,
-        interfaces::ReportManager& reportManager,
-        interfaces::JsonStorage& reportStorage) const override;
+    std::unique_ptr<interfaces::Report>
+        make(boost::asio::yield_context& yield, const std::string& name,
+             const std::string& reportingType, bool emitsReadingsSignal,
+             bool logToMetricReportsCollection,
+             std::chrono::milliseconds period,
+             const ReadingParameters& metricParams,
+             interfaces::ReportManager& reportManager,
+             interfaces::JsonStorage& reportStorage) const override;
+    std::unique_ptr<interfaces::Report>
+        make(const std::string& name, const std::string& reportingType,
+             bool emitsReadingsSignal, bool logToMetricReportsCollection,
+             std::chrono::milliseconds period,
+             const ReadingParameters& metricParams,
+             interfaces::ReportManager& reportManager,
+             interfaces::JsonStorage& reportStorage,
+             std::vector<LabeledMetricParameters> labeledMetricParams)
+            const override;
 
   private:
     using SensorPath = std::string;
@@ -30,10 +40,10 @@
     using SensorTree = std::pair<SensorPath, SensorIfaces>;
 
     std::vector<std::shared_ptr<interfaces::Sensor>> getSensors(
-        const std::optional<std::vector<SensorTree>>& tree,
-        const std::vector<sdbusplus::message::object_path>& sensorPaths) const;
-    std::vector<SensorTree>
-        getSensorTree(boost::asio::yield_context& yield) const;
+        const std::vector<LabeledSensorParameters>& sensorPaths) const;
+    std::vector<LabeledMetricParameters>
+        convertMetricParams(boost::asio::yield_context& yield,
+                            const ReadingParameters& metricParams) const;
 
     std::shared_ptr<sdbusplus::asio::connection> bus;
     std::shared_ptr<sdbusplus::asio::object_server> objServer;
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index eb2d767..f9840d3 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -1,6 +1,8 @@
 #include "report_manager.hpp"
 
+#include "interfaces/types.hpp"
 #include "report.hpp"
+#include "utils/transform.hpp"
 
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/exception.hpp>
@@ -35,13 +37,13 @@
                                     const bool emitsReadingsUpdate,
                                     const bool logToMetricReportsCollection,
                                     const uint64_t interval,
-                                    const ReadingParameters& metricParams) {
+                                    ReadingParameters metricParams) {
                     return addReport(yield, reportName, reportingType,
                                      emitsReadingsUpdate,
                                      logToMetricReportsCollection,
                                      std::chrono::milliseconds(interval),
-                                     metricParams)
-                        ->getPath();
+                                     std::move(metricParams))
+                        .getPath();
                 });
         });
 }
@@ -54,11 +56,8 @@
         reports.end());
 }
 
-std::unique_ptr<interfaces::Report>& ReportManager::addReport(
-    std::optional<std::reference_wrapper<boost::asio::yield_context>> yield,
-    const std::string& reportName, const std::string& reportingType,
-    const bool emitsReadingsUpdate, const bool logToMetricReportsCollection,
-    std::chrono::milliseconds interval, const ReadingParameters& metricParams)
+void ReportManager::verifyAddReport(const std::string& reportName,
+                                    std::chrono::milliseconds interval)
 {
     if (reports.size() >= maxReports)
     {
@@ -81,12 +80,49 @@
         throw sdbusplus::exception::SdBusError(
             static_cast<int>(std::errc::invalid_argument), "Invalid interval");
     }
+}
 
-    reports.emplace_back(
-        reportFactory->make(yield, reportName, reportingType,
-                            emitsReadingsUpdate, logToMetricReportsCollection,
-                            interval, metricParams, *this, *reportStorage));
-    return reports.back();
+interfaces::Report& ReportManager::addReport(
+    boost::asio::yield_context& yield, const std::string& reportName,
+    const std::string& reportingType, const bool emitsReadingsUpdate,
+    const bool logToMetricReportsCollection, std::chrono::milliseconds interval,
+    ReadingParameters metricParams)
+{
+    verifyAddReport(reportName, interval);
+
+    reports.emplace_back(reportFactory->make(
+        yield, reportName, reportingType, emitsReadingsUpdate,
+        logToMetricReportsCollection, interval, std::move(metricParams), *this,
+        *reportStorage));
+    return *reports.back();
+}
+
+interfaces::Report& ReportManager::addReport(
+    const std::string& reportName, const std::string& reportingType,
+    const bool emitsReadingsUpdate, const bool logToMetricReportsCollection,
+    std::chrono::milliseconds interval,
+    std::vector<LabeledMetricParameters> labeledMetricParams)
+{
+    verifyAddReport(reportName, interval);
+
+    auto metricParams = utils::transform(
+        labeledMetricParams, [](const LabeledMetricParameters& param) {
+            using namespace utils::tstring;
+
+            return ReadingParameters::value_type(
+                utils::transform(param.at_index<0>(),
+                                 [](const LabeledSensorParameters& p) {
+                                     return sdbusplus::message::object_path(
+                                         p.at_label<Path>());
+                                 }),
+                param.at_index<1>(), param.at_index<2>(), param.at_index<3>());
+        });
+
+    reports.emplace_back(reportFactory->make(
+        reportName, reportingType, emitsReadingsUpdate,
+        logToMetricReportsCollection, interval, std::move(metricParams), *this,
+        *reportStorage, labeledMetricParams));
+    return *reports.back();
 }
 
 void ReportManager::loadFromPersistent()
@@ -112,16 +148,14 @@
             bool logToMetricReportsCollection =
                 data->at("LogToMetricReportsCollection").get<bool>();
             uint64_t interval = data->at("Interval").get<uint64_t>();
-            ReadingParameters readingParameters;
-            for (auto& item : data->at("ReadingParameters"))
-            {
-                readingParameters.emplace_back(
-                    LabeledReadingParameter::from_json(item));
-            }
+            auto readingParameters =
+                data->at("ReadingParameters")
+                    .get<std::vector<LabeledMetricParameters>>();
 
-            addReport(std::nullopt, name, reportingType, emitsReadingsSignal,
+            addReport(name, reportingType, emitsReadingsSignal,
                       logToMetricReportsCollection,
-                      std::chrono::milliseconds(interval), readingParameters);
+                      std::chrono::milliseconds(interval),
+                      std::move(readingParameters));
         }
         catch (const std::exception& e)
         {
diff --git a/src/report_manager.hpp b/src/report_manager.hpp
index a26b59e..0e37144 100644
--- a/src/report_manager.hpp
+++ b/src/report_manager.hpp
@@ -35,12 +35,20 @@
     std::unique_ptr<sdbusplus::asio::dbus_interface> reportManagerIface;
     std::vector<std::unique_ptr<interfaces::Report>> reports;
 
-    std::unique_ptr<interfaces::Report>& addReport(
-        std::optional<std::reference_wrapper<boost::asio::yield_context>> yield,
+    void verifyAddReport(const std::string& reportName,
+                         std::chrono::milliseconds interval);
+    interfaces::Report& addReport(boost::asio::yield_context& yield,
+                                  const std::string& reportName,
+                                  const std::string& reportingType,
+                                  const bool emitsReadingsUpdate,
+                                  const bool logToMetricReportsCollection,
+                                  std::chrono::milliseconds interval,
+                                  ReadingParameters metricParams);
+    interfaces::Report& addReport(
         const std::string& reportName, const std::string& reportingType,
         const bool emitsReadingsUpdate, const bool logToMetricReportsCollection,
         std::chrono::milliseconds interval,
-        const ReadingParameters& metricParams);
+        std::vector<LabeledMetricParameters> metricParams);
     void loadFromPersistent();
 
   public:
diff --git a/src/sensor.cpp b/src/sensor.cpp
index c4973cd..8a6f673 100644
--- a/src/sensor.cpp
+++ b/src/sensor.cpp
@@ -53,6 +53,11 @@
 void Sensor::registerForUpdates(
     const std::weak_ptr<interfaces::SensorListener>& weakListener)
 {
+    listeners.erase(
+        std::remove_if(listeners.begin(), listeners.end(),
+                       [](const auto& listener) { return listener.expired(); }),
+        listeners.end());
+
     if (auto listener = weakListener.lock())
     {
         listeners.emplace_back(weakListener);
diff --git a/src/utils/labeled_tuple.hpp b/src/utils/labeled_tuple.hpp
index 7e75322..795f37f 100644
--- a/src/utils/labeled_tuple.hpp
+++ b/src/utils/labeled_tuple.hpp
@@ -53,54 +53,93 @@
 template <class... Args, class... Labels>
 struct LabeledTuple<std::tuple<Args...>, Labels...>
 {
-    LabeledTuple() = delete;
-
     static_assert(sizeof...(Args) == sizeof...(Labels));
 
-    static nlohmann::json to_json(const std::tuple<Args...>& tuple)
+    using tuple_type = std::tuple<Args...>;
+
+    LabeledTuple() = default;
+    LabeledTuple(const LabeledTuple&) = default;
+    LabeledTuple(LabeledTuple&&) = default;
+
+    LabeledTuple(tuple_type v) : value(std::move(v))
+    {}
+    LabeledTuple(Args... args) : value(std::move(args)...)
+    {}
+
+    LabeledTuple& operator=(const LabeledTuple&) = default;
+    LabeledTuple& operator=(LabeledTuple&&) = default;
+
+    nlohmann::json to_json() const
     {
         nlohmann::json j;
-        to_json_all(j, tuple, std::make_index_sequence<sizeof...(Args)>());
+        to_json_all(j, std::make_index_sequence<sizeof...(Args)>());
         return j;
     }
 
-    static std::tuple<Args...> from_json(const nlohmann::json& j)
+    void from_json(const nlohmann::json& j)
     {
-        std::tuple<Args...> value;
-        from_json_all(j, value, std::make_index_sequence<sizeof...(Args)>());
-        return value;
+        from_json_all(j, std::make_index_sequence<sizeof...(Args)>());
+    }
+
+    template <size_t Idx>
+    const auto& at_index() const
+    {
+        return std::get<Idx>(value);
+    }
+
+    template <size_t Idx>
+    auto& at_index()
+    {
+        return std::get<Idx>(value);
+    }
+
+    template <class Label>
+    const auto& at_label() const
+    {
+        return find_item<0, Label>(*this);
+    }
+
+    template <class Label>
+    auto& at_label()
+    {
+        return find_item<0, Label>(*this);
+    }
+
+    bool operator==(const LabeledTuple& other) const
+    {
+        return value == other.value;
+    }
+
+    bool operator<(const LabeledTuple& other) const
+    {
+        return value < other.value;
     }
 
   private:
     template <size_t... Idx>
-    static void to_json_all(nlohmann::json& j, const std::tuple<Args...>& tuple,
-                            std::index_sequence<Idx...>)
+    void to_json_all(nlohmann::json& j, std::index_sequence<Idx...>) const
     {
-        (to_json_item<Idx>(j, tuple), ...);
+        (to_json_item<Idx>(j), ...);
     }
 
     template <size_t Idx>
-    static void to_json_item(nlohmann::json& j,
-                             const std::tuple<Args...>& tuple)
+    void to_json_item(nlohmann::json& j) const
     {
         using Label = std::tuple_element_t<Idx, std::tuple<Labels...>>;
-        j[Label::str()] = std::get<Idx>(tuple);
+        j[Label::str()] = std::get<Idx>(value);
     }
 
     template <size_t... Idx>
-    static void from_json_all(const nlohmann::json& j,
-                              std::tuple<Args...>& value,
-                              std::index_sequence<Idx...>)
+    void from_json_all(const nlohmann::json& j, std::index_sequence<Idx...>)
     {
-        (from_json_item<Idx>(j, value), ...);
+        (from_json_item<Idx>(j), ...);
     }
 
     template <size_t Idx>
-    static void from_json_item(const nlohmann::json& j,
-                               std::tuple<Args...>& value)
+    void from_json_item(const nlohmann::json& j)
     {
         using Label = std::tuple_element_t<Idx, std::tuple<Labels...>>;
-        using T = std::tuple_element_t<Idx, std::tuple<Args...>>;
+        using T = std::tuple_element_t<Idx, tuple_type>;
         const nlohmann::json& item = j.at(Label::str());
         if constexpr (detail::has_utils_from_json_v<T>)
         {
@@ -112,6 +151,36 @@
             std::get<Idx>(value) = item.get<T>();
         }
     }
+
+    template <size_t Idx, class Label, class Self>
+    static auto& find_item(Self& self)
+    {
+        if constexpr (std::is_same_v<Label, std::tuple_element_t<
+                                                Idx, std::tuple<Labels...>>>)
+        {
+            return std::get<Idx>(self.value);
+        }
+        else
+        {
+            return find_item<Idx + 1, Label>(self);
+        }
+    }
+
+    tuple_type value;
 };
 
+template <class... Args, class... Labels>
+void to_json(nlohmann::json& json,
+             const LabeledTuple<std::tuple<Args...>, Labels...>& tuple)
+{
+    json = tuple.to_json();
+}
+
+template <class... Args, class... Labels>
+void from_json(const nlohmann::json& json,
+               LabeledTuple<std::tuple<Args...>, Labels...>& tuple)
+{
+    tuple.from_json(json);
+}
+
 } // namespace utils
diff --git a/src/utils/tstring.hpp b/src/utils/tstring.hpp
index 7fcddb3..f0a401f 100644
--- a/src/utils/tstring.hpp
+++ b/src/utils/tstring.hpp
@@ -5,32 +5,56 @@
 
 namespace utils
 {
-namespace literals
-{
-
-constexpr char id[] = "id";
-constexpr char sensorPaths[] = "sensorPaths";
-constexpr char operationType[] = "operationType";
-constexpr char metricMetadata[] = "metricMetadata";
-
-} // namespace literals
-
-template <const char* const V>
-struct Label
-{
-    static std::string str()
-    {
-        return V;
-    }
-};
-
 namespace tstring
 {
 
-using Id = utils::Label<utils::literals::id>;
-using SensorPaths = utils::Label<utils::literals::sensorPaths>;
-using OperationType = utils::Label<utils::literals::operationType>;
-using MetricMetadata = utils::Label<utils::literals::metricMetadata>;
+struct Id
+{
+    static std::string str()
+    {
+        return "id";
+    }
+};
+
+struct SensorPaths
+{
+    static std::string str()
+    {
+        return "sensorPaths";
+    }
+};
+
+struct OperationType
+{
+    static std::string str()
+    {
+        return "operationType";
+    }
+};
+
+struct MetricMetadata
+{
+    static std::string str()
+    {
+        return "metricMetadata";
+    }
+};
+
+struct Service
+{
+    static std::string str()
+    {
+        return "service";
+    }
+};
+
+struct Path
+{
+    static std::string str()
+    {
+        return "path";
+    }
+};
 
 } // namespace tstring
 } // namespace utils