From ab0342824a5de027e32347756596cc79ec2a3335 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ren=C3=A9=20Paris?= <rene.paris@in-tech.com>
Date: Wed, 19 Mar 2025 14:52:11 +0000
Subject: [PATCH] feat(PropertyInterpreter): Generic structure for custom
 property interpretation

---
 .../exponential_distribution.json             |  20 +++
 doc/json_schemas/gamma_distribution.json      |  28 +++
 doc/json_schemas/lognormal_distribution.json  |  20 +++
 doc/json_schemas/normal_distrubution.json     |  28 +++
 .../schemas => doc/json_schemas}/readme.md    |   0
 doc/json_schemas/uniform_distribution.json    |  18 ++
 engine/CMakeLists.txt                         |   4 +-
 engine/cmake/generated_files.cmake            |  15 ++
 engine/src/Utils/JsonParsing.cpp              |  59 +++++++
 engine/src/Utils/JsonParsing.h                |  63 +++++++
 .../Property/Distribution/BaseDistribution.h  |  32 ----
 .../Distribution/ExponentialDistribution.h    |  28 ---
 .../Property/Distribution/GammaDistribution.h |  29 ----
 .../Distribution/LogNormalDistribution.h      |  29 ----
 .../Distribution/NormalDistribution.h         |  29 ----
 engine/src/Utils/Property/JsonParser.h        |  89 ----------
 .../distributions/base_distribution.json      |  20 ---
 .../exponential_distribution.json             |  30 ----
 .../distributions/gamma_distribution.json     |  34 ----
 .../distributions/lognormal_distribution.json |  35 ----
 .../distributions/normal_distrubution.json    |  35 ----
 .../distributions/uniform_distribution.json   |  24 ---
 .../DistributionInterpreter.cpp               |  65 +++++++
 .../DistributionInterpreter.h                 | 104 ++++++++++++
 .../Distributions.h                           |  11 +-
 .../Distributions/ExponentialDistribution.h   |  31 ++++
 .../Distributions/GammaDistribution.h         |  39 +++++
 .../Distributions/LogNormalDistribution.h     |  33 ++++
 .../Distributions/NormalDistribution.h        |  33 ++++
 .../Distributions}/UniformDistribution.h      |  17 +-
 .../PropertyInterpretation/Interpreter.h      | 123 ++++++++++++++
 .../PropertyInterpretation/JsonInterpreter.h  |  43 +++++
 .../PropertyInterpretation/RawValueRelay.h    |  43 +++++
 engine/src/Utils/PropertyInterpreter.cpp      |  71 ++++++++
 engine/src/Utils/PropertyInterpreter.h        |  53 ++++++
 engine/tests/Utils/DistributionTest.cpp       | 160 ++++++++++++++++++
 engine/tests/Utils/JsonParserTest.cpp         | 106 ------------
 engine/tests/Utils/JsonParsingTest.cpp        |  80 +++++++++
 .../tests/Utils/PropertyInterpreterTest.cpp   | 142 ++++++++++++++++
 39 files changed, 1288 insertions(+), 535 deletions(-)
 create mode 100644 doc/json_schemas/exponential_distribution.json
 create mode 100644 doc/json_schemas/gamma_distribution.json
 create mode 100644 doc/json_schemas/lognormal_distribution.json
 create mode 100644 doc/json_schemas/normal_distrubution.json
 rename {engine/src/Utils/Property/schemas => doc/json_schemas}/readme.md (100%)
 create mode 100644 doc/json_schemas/uniform_distribution.json
 create mode 100644 engine/src/Utils/JsonParsing.cpp
 create mode 100644 engine/src/Utils/JsonParsing.h
 delete mode 100644 engine/src/Utils/Property/Distribution/BaseDistribution.h
 delete mode 100644 engine/src/Utils/Property/Distribution/ExponentialDistribution.h
 delete mode 100644 engine/src/Utils/Property/Distribution/GammaDistribution.h
 delete mode 100644 engine/src/Utils/Property/Distribution/LogNormalDistribution.h
 delete mode 100644 engine/src/Utils/Property/Distribution/NormalDistribution.h
 delete mode 100644 engine/src/Utils/Property/JsonParser.h
 delete mode 100644 engine/src/Utils/Property/schemas/distributions/base_distribution.json
 delete mode 100644 engine/src/Utils/Property/schemas/distributions/exponential_distribution.json
 delete mode 100644 engine/src/Utils/Property/schemas/distributions/gamma_distribution.json
 delete mode 100644 engine/src/Utils/Property/schemas/distributions/lognormal_distribution.json
 delete mode 100644 engine/src/Utils/Property/schemas/distributions/normal_distrubution.json
 delete mode 100644 engine/src/Utils/Property/schemas/distributions/uniform_distribution.json
 create mode 100644 engine/src/Utils/PropertyInterpretation/DistributionInterpreter.cpp
 create mode 100644 engine/src/Utils/PropertyInterpretation/DistributionInterpreter.h
 rename engine/src/Utils/{Property => PropertyInterpretation}/Distributions.h (63%)
 create mode 100644 engine/src/Utils/PropertyInterpretation/Distributions/ExponentialDistribution.h
 create mode 100644 engine/src/Utils/PropertyInterpretation/Distributions/GammaDistribution.h
 create mode 100644 engine/src/Utils/PropertyInterpretation/Distributions/LogNormalDistribution.h
 create mode 100644 engine/src/Utils/PropertyInterpretation/Distributions/NormalDistribution.h
 rename engine/src/Utils/{Property/Distribution => PropertyInterpretation/Distributions}/UniformDistribution.h (56%)
 create mode 100644 engine/src/Utils/PropertyInterpretation/Interpreter.h
 create mode 100644 engine/src/Utils/PropertyInterpretation/JsonInterpreter.h
 create mode 100644 engine/src/Utils/PropertyInterpretation/RawValueRelay.h
 create mode 100644 engine/src/Utils/PropertyInterpreter.cpp
 create mode 100644 engine/src/Utils/PropertyInterpreter.h
 create mode 100644 engine/tests/Utils/DistributionTest.cpp
 delete mode 100644 engine/tests/Utils/JsonParserTest.cpp
 create mode 100644 engine/tests/Utils/JsonParsingTest.cpp
 create mode 100644 engine/tests/Utils/PropertyInterpreterTest.cpp

diff --git a/doc/json_schemas/exponential_distribution.json b/doc/json_schemas/exponential_distribution.json
new file mode 100644
index 00000000..1d294b38
--- /dev/null
+++ b/doc/json_schemas/exponential_distribution.json
@@ -0,0 +1,20 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "ExponentialDistribution",
+  "type": "object",
+  "properties": {
+      "lowerLimit": {
+          "type": "number",
+          "description": "Lower limit"
+      },
+      "upperLimit": {
+          "type": "number",
+          "description": "Upper limit"
+      },
+      "expectedValue": {
+          "type": "number",
+          "description": "Reciprocal of the rate parameter lambda"
+      }
+  },
+  "required": ["lowerLimit", "upperLimit", "expectedValue"]
+}
\ No newline at end of file
diff --git a/doc/json_schemas/gamma_distribution.json b/doc/json_schemas/gamma_distribution.json
new file mode 100644
index 00000000..479cb799
--- /dev/null
+++ b/doc/json_schemas/gamma_distribution.json
@@ -0,0 +1,28 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "GammaDistribution",
+  "type": "object",
+  "properties": {
+      "lowerLimit": {
+          "type": "number",
+          "description": "Lower limit",
+          "minimum": 0.0
+      },
+      "upperLimit": {
+          "type": "number",
+          "description": "Upper limit",
+          "minimum": 0.0
+      },
+      "expectedValue": {
+          "type": "number",
+          "description": "Expected value (mean) of the distribution, calculated as alpha / beta",
+          "minimum": 0.0
+      },
+      "variance": {
+          "type": "number",
+          "description": "Variance of the distribution, calculated as alpha / beta^2",
+          "minimum": 0.0
+      }
+  },
+  "required": ["lowerLimit", "upperLimit", "expectedValue", "variance"]
+}
\ No newline at end of file
diff --git a/doc/json_schemas/lognormal_distribution.json b/doc/json_schemas/lognormal_distribution.json
new file mode 100644
index 00000000..1d294b38
--- /dev/null
+++ b/doc/json_schemas/lognormal_distribution.json
@@ -0,0 +1,20 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "ExponentialDistribution",
+  "type": "object",
+  "properties": {
+      "lowerLimit": {
+          "type": "number",
+          "description": "Lower limit"
+      },
+      "upperLimit": {
+          "type": "number",
+          "description": "Upper limit"
+      },
+      "expectedValue": {
+          "type": "number",
+          "description": "Reciprocal of the rate parameter lambda"
+      }
+  },
+  "required": ["lowerLimit", "upperLimit", "expectedValue"]
+}
\ No newline at end of file
diff --git a/doc/json_schemas/normal_distrubution.json b/doc/json_schemas/normal_distrubution.json
new file mode 100644
index 00000000..3021ae8e
--- /dev/null
+++ b/doc/json_schemas/normal_distrubution.json
@@ -0,0 +1,28 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "NormalDistribution",
+  "type": "object",
+  "properties": {
+      "lowerLimit": {
+          "type": "number",
+          "description": "Lower limit",
+          "minimum": 0.0
+      },
+      "upperLimit": {
+          "type": "number",
+          "description": "Upper limit",
+          "minimum": 0.0
+      },
+      "expectedValue": {
+          "type": "number",
+          "description": "Expected value",
+          "minimum": 0.0
+      },
+      "variance": {
+          "type": "number",
+          "description": "Variance (square of standard deviation)",
+          "minimum": 0.0
+      }
+  },
+  "required": ["lowerLimit", "upperLimit", "expectedValue", "variance"]
+}
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/readme.md b/doc/json_schemas/readme.md
similarity index 100%
rename from engine/src/Utils/Property/schemas/readme.md
rename to doc/json_schemas/readme.md
diff --git a/doc/json_schemas/uniform_distribution.json b/doc/json_schemas/uniform_distribution.json
new file mode 100644
index 00000000..1532d5e2
--- /dev/null
+++ b/doc/json_schemas/uniform_distribution.json
@@ -0,0 +1,18 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "title": "UniformDistribution",
+  "type": "object",
+  "properties": {
+      "lowerLimit": {
+          "type": "number",
+          "description": "Lower limit",
+          "minimum": 0.0
+      },
+      "upperLimit": {
+          "type": "number",
+          "description": "Upper limit",
+          "minimum": 0.0
+      }
+  },
+  "required": ["lowerLimit", "upperLimit"]
+}
\ No newline at end of file
diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt
index db616255..03502ba3 100644
--- a/engine/CMakeLists.txt
+++ b/engine/CMakeLists.txt
@@ -219,10 +219,12 @@ target_sources(
           ${CMAKE_CURRENT_LIST_DIR}/tests/TrafficSignalParsing/TreeGenerationTest.cpp
           ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/ConstantsTest.cpp
           ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/ControllerCreatorTest.cpp
+          ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/DistributionTest.cpp
           ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/EllipseTest.cpp
           ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/EntityCreatorTest.cpp
           ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/EntityUtilsTest.cpp
-          ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/JsonParserTest.cpp
+          ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/JsonParsingTest.cpp
+          ${CMAKE_CURRENT_LIST_DIR}/tests/Utils/PropertyInterpreterTest.cpp
 )
 
 add_custom_command(TARGET ${PROJECT_NAME}Test
diff --git a/engine/cmake/generated_files.cmake b/engine/cmake/generated_files.cmake
index 20fd7859..4712dfd1 100644
--- a/engine/cmake/generated_files.cmake
+++ b/engine/cmake/generated_files.cmake
@@ -239,6 +239,9 @@ list(APPEND ${PROJECT_NAME}_SOURCES
     src/Utils/EntityUtils.cpp
     src/Utils/EventPrioritizer.cpp
     src/Utils/Logger.cpp
+    src/Utils/PropertyInterpretation/DistributionInterpreter.cpp
+    src/Utils/PropertyInterpreter.cpp
+    src/Utils/JsonParsing.cpp
     src/Utils/TrafficSignalBuilder.cpp
 )
 
@@ -606,5 +609,17 @@ list(APPEND ${PROJECT_NAME}_HEADERS
     src/Utils/IControllerService.h
     src/Utils/IEventPrioritizer.h
     src/Utils/Logger.h
+    src/Utils/PropertyInterpretation/DistributionInterpreter.h
+    src/Utils/PropertyInterpretation/Distributions.h
+    src/Utils/PropertyInterpretation/Distributions/ExponentialDistribution.h
+    src/Utils/PropertyInterpretation/Distributions/GammaDistribution.h
+    src/Utils/PropertyInterpretation/Distributions/LogNormalDistribution.h
+    src/Utils/PropertyInterpretation/Distributions/NormalDistribution.h
+    src/Utils/PropertyInterpretation/Distributions/UniformDistribution.h
+    src/Utils/PropertyInterpretation/Interpreter.h
+    src/Utils/PropertyInterpretation/JsonInterpreter.h
+    src/Utils/PropertyInterpretation/RawValueRelay.h
+    src/Utils/PropertyInterpreter.h
+    src/Utils/JsonParsing.h
     src/Utils/TrafficSignalBuilder.h
 )
diff --git a/engine/src/Utils/JsonParsing.cpp b/engine/src/Utils/JsonParsing.cpp
new file mode 100644
index 00000000..6e4c0ec0
--- /dev/null
+++ b/engine/src/Utils/JsonParsing.cpp
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+#include "JsonParsing.h"
+
+#include <regex>
+
+namespace OpenScenarioEngine::v1_3
+{
+
+namespace detail
+{
+
+/// Replace all occurrences of a string in a string
+/// @param input   String to search in
+/// @param search  String to search for
+/// @param replace String to replace with
+/// @return String with all occurrences of search replaced with replace
+std::string Replace(std::string_view input, const std::string& search, const std::string& replace)
+{
+  std::regex search_regex(search);
+  return std::regex_replace(std::string(input), search_regex, replace);
+}
+
+}  // namespace detail
+
+namespace json
+{
+
+std::optional<nlohmann::json> TryParse(std::string_view property)
+{
+  if (auto j = nlohmann::json::parse(std::string(property), nullptr, false);
+      !j.is_discarded())
+  {
+    return j;
+  }
+
+  for (const std::string& search : {"&quot;", "'"})
+  {
+    const auto modified_property = detail::Replace(property, search, "\"");
+    if (auto j = nlohmann::json::parse(modified_property, nullptr, false);
+        !j.is_discarded())
+    {
+      return j;
+    }
+  }
+
+  return std::nullopt;
+}
+
+}  // namespace json
+}  // namespace OpenScenarioEngine::v1_3
\ No newline at end of file
diff --git a/engine/src/Utils/JsonParsing.h b/engine/src/Utils/JsonParsing.h
new file mode 100644
index 00000000..ba716982
--- /dev/null
+++ b/engine/src/Utils/JsonParsing.h
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <optional>
+#include <regex>
+#include <string>
+#include <string_view>
+
+#include "Utils/Logger.h"
+
+namespace OpenScenarioEngine::v1_3
+{
+
+namespace json
+{
+
+/// Try to parse a JSON object from a string
+///
+/// In a first attempt, this function tries to parse the string as it is.
+/// But as the main use case for the parser is interpreting XML properties
+/// as JSON objects and the JSON standard requires double quotes for strings,
+/// the XML parser will try to escape double quotes in the XML file using &quot;
+/// or try with single quotes (not JSON standard, but we support it).
+/// @param value Value to be parsed
+/// @return Parsed JSON object or nullopt if parsing failed
+std::optional<nlohmann::json> TryParse(std::string_view value);
+
+/// Deserialize a string into a object using nlohmann::json
+/// @tparam DeserializedType  Type of the object to deserialize (e.g. a distribution)
+/// @param value           String to deserialize
+/// @return Deserialized object of type T or empty optional if deserialization failed
+template <typename DeserializedType>
+std::optional<DeserializedType> Deserialize(std::string_view value)
+{
+  try
+  {
+    if (auto j = TryParse(value))
+    {
+      return j->get<DeserializedType>();
+    }
+    Logger::Error("JsonParser: \'" + std::string(value) + "' is not a valid JSON object");
+  }
+  catch (const nlohmann::json::out_of_range&)
+  {
+    Logger::Error("JsonParser: \'" + std::string(value) + "' JSON object does not match expected object");
+  }
+
+  return std::nullopt;
+}
+
+}  // namespace json
+
+}  // namespace OpenScenarioEngine::v1_3
\ No newline at end of file
diff --git a/engine/src/Utils/Property/Distribution/BaseDistribution.h b/engine/src/Utils/Property/Distribution/BaseDistribution.h
deleted file mode 100644
index 30c7e68d..00000000
--- a/engine/src/Utils/Property/Distribution/BaseDistribution.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *******************************************************************************/
-
-#pragma once
-
-#include <nlohmann/json.hpp>
-#include <string>
-
-namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
-{
-
-/// @brief Base class for all distribution types
-///
-/// Distributions and their values need to be defined by
-/// means of string properties. For convenience, they can
-/// be deserialized from a JSON into individual distributions.
-/// See schemas/distributions/*.json for examples.
-struct BaseDistribution
-{
-  std::string type;  ///!< Type of the distribution
-  double min;        ///!< Minimum value
-  double max;        ///!< Maximum value
-};
-
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
diff --git a/engine/src/Utils/Property/Distribution/ExponentialDistribution.h b/engine/src/Utils/Property/Distribution/ExponentialDistribution.h
deleted file mode 100644
index 9fc2b083..00000000
--- a/engine/src/Utils/Property/Distribution/ExponentialDistribution.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *******************************************************************************/
-
-#pragma once
-
-#include <nlohmann/json.hpp>
-
-#include "BaseDistribution.h"
-
-namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
-{
-
-/// @brief Normal distribution
-struct ExponentialDistribution : public BaseDistribution
-{
-  double lamda;  ///!< Rate parameter
-};
-
-NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ExponentialDistribution, type, min, max, lamda);
-
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
diff --git a/engine/src/Utils/Property/Distribution/GammaDistribution.h b/engine/src/Utils/Property/Distribution/GammaDistribution.h
deleted file mode 100644
index 703c3d02..00000000
--- a/engine/src/Utils/Property/Distribution/GammaDistribution.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *******************************************************************************/
-
-#pragma once
-
-#include <nlohmann/json.hpp>
-
-#include "BaseDistribution.h"
-
-namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
-{
-
-/// @brief Normal distribution
-struct GammaDistribution : public BaseDistribution
-{
-  double shape;  ///!< Shape parameter alpha
-  double scale;  ///!< Scale parameter theta
-};
-
-NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GammaDistribution, type, min, max, shape, scale);
-
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
diff --git a/engine/src/Utils/Property/Distribution/LogNormalDistribution.h b/engine/src/Utils/Property/Distribution/LogNormalDistribution.h
deleted file mode 100644
index a78cb842..00000000
--- a/engine/src/Utils/Property/Distribution/LogNormalDistribution.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *******************************************************************************/
-
-#pragma once
-
-#include <nlohmann/json.hpp>
-
-#include "BaseDistribution.h"
-
-namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
-{
-
-/// @brief Log-normal distribution
-struct LogNormalDistribution : public BaseDistribution
-{
-  double mu;     ///!< Mean value
-  double sigma;  ///!< Standard deviation
-};
-
-NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LogNormalDistribution, type, min, max, mu, sigma);
-
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
diff --git a/engine/src/Utils/Property/Distribution/NormalDistribution.h b/engine/src/Utils/Property/Distribution/NormalDistribution.h
deleted file mode 100644
index aa25d000..00000000
--- a/engine/src/Utils/Property/Distribution/NormalDistribution.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *******************************************************************************/
-
-#pragma once
-
-#include <nlohmann/json.hpp>
-
-#include "BaseDistribution.h"
-
-namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
-{
-
-/// @brief Normal distribution
-struct NormalDistribution : public BaseDistribution
-{
-  double mean;    ///!< Mean value
-  double stddev;  ///!< Standard deviation
-};
-
-NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NormalDistribution, type, min, max, mean, stddev);
-
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
diff --git a/engine/src/Utils/Property/JsonParser.h b/engine/src/Utils/Property/JsonParser.h
deleted file mode 100644
index 706368ee..00000000
--- a/engine/src/Utils/Property/JsonParser.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
- *
- * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
- * which is available at https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *******************************************************************************/
-
-#pragma once
-
-#include <nlohmann/json.hpp>
-#include <optional>
-#include <regex>
-#include <string_view>
-
-#include "Utils/Logger.h"
-
-namespace OpenScenarioEngine::v1_3::Utils::Property
-{
-
-namespace detail
-{
-
-/// Replace all occurrences of a string in a string
-/// @param input   String to search in
-/// @param search  String to search for
-/// @param replace String to replace with
-/// @return String with all occurrences of search replaced with replace
-std::string Replace(std::string_view input, const std::string& search, const std::string& replace)
-{
-  std::regex search_regex(search);
-  return std::regex_replace(std::string(input), search_regex, replace);
-}
-
-/// Try to parse a JSON object from a property string
-///
-/// In a first attempt, this function tries to parse the string as it is.
-/// But as the main use case for the parser is interpreting XML properties
-/// as JSON objects and the JSON standard requires double quotes for strings,
-/// the XML parser will try to escape double quotes in the XML file using &quot;
-/// or try with single quotes (not JSON standard, but we support it).
-/// @tparam DeserializedType Type of the object to deserialize (e.g. a distribution)
-/// @param property Property to be parsed
-/// @return Parsed JSON object or throw if parsing failed
-template <typename DeserializedType>
-std::optional<DeserializedType> ParseJson(std::string_view property)
-{
-  if (auto json = nlohmann::json::parse(std::string(property), nullptr, false);
-      !json.is_discarded())
-  {
-    return json.get<DeserializedType>();
-  }
-
-  constexpr const char* DOUBLE_QUOTE{"\""};
-  for (const std::string& search : {"&quot;", "'"})
-  {
-    if (auto json = nlohmann::json::parse(Replace(property, search, DOUBLE_QUOTE), nullptr, false);
-        !json.is_discarded())
-    {
-      return json.get<DeserializedType>();
-    }
-  }
-
-  return std::nullopt;
-}
-
-}  // namespace detail
-
-/// Deserialize a property string into a object using nlohmann::json
-/// @tparam DeserializedType  Type of the object to deserialize (e.g. a distribution)
-/// @param property           Property string to deserialize
-/// @return Deserialized object of type T or empty optional if deserialization failed
-template <typename DeserializedType>
-std::optional<DeserializedType> ReadFromJson(std::string_view property)
-{
-  try
-  {
-    return detail::ParseJson<DeserializedType>(property);
-  }
-  catch (const nlohmann::json::out_of_range&)
-  {
-    Logger::Error("JsonParser: Property '" + std::string(property) + "' does not match expected object");
-  }
-  return std::nullopt;
-}
-
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/distributions/base_distribution.json b/engine/src/Utils/Property/schemas/distributions/base_distribution.json
deleted file mode 100644
index cc55de3e..00000000
--- a/engine/src/Utils/Property/schemas/distributions/base_distribution.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "BaseDistribution",
-  "type": "object",
-  "properties": {
-    "type": {
-      "type": "string",
-      "description": "The type of the distribution, e.g. 'uniform', 'normal', etc."
-    },
-    "min": {
-      "type": "number",
-      "description": "The minimum value of the distribution"
-    },
-    "max": {
-      "type": "number",
-      "description": "The maximum value of the distribution"
-    }
-  },
-  "required": ["min", "max"]
-}
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/distributions/exponential_distribution.json b/engine/src/Utils/Property/schemas/distributions/exponential_distribution.json
deleted file mode 100644
index 60eae64c..00000000
--- a/engine/src/Utils/Property/schemas/distributions/exponential_distribution.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "ExponentialDistribution",
-  "allOf": [
-    {
-      "$ref": "base_distribution.json"
-    },
-    {
-      "if": {
-        "properties": {
-          "type": {
-            "enum": [
-              "exponential", "Exponential", "ExponentialDistribution", "exponential_distribution",
-              "Exponential_Distribution", "exponential-distribution", "Exponential-Distribution",
-              "exponentialDistribution", "exponentialdistribution"]
-          }
-        }
-      },
-      "then": {
-         "properties": {
-           "lambda": {
-             "type": "number",
-             "description": "Rate parameter of the distribution"
-           }
-        },
-        "required": ["type", "min", "max", "lambda"]
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/distributions/gamma_distribution.json b/engine/src/Utils/Property/schemas/distributions/gamma_distribution.json
deleted file mode 100644
index c07cb9cf..00000000
--- a/engine/src/Utils/Property/schemas/distributions/gamma_distribution.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "GammaDistribution",
-  "allOf": [
-    {
-      "$ref": "base_distribution.json"
-    },
-    {
-      "if": {
-        "properties": {
-          "type": {
-            "enum": [
-              "gamma", "Gamma", "GammaDistribution", "gamma_distribution",
-              "Gamma_Distribution", "gamma-distribution", "Gamma-Distribution",
-              "gammaDistribution", "gammadistribution"]
-          }
-        }
-      },
-      "then": {
-         "properties": {
-           "shape": {
-             "type": "number",
-             "description": "shape parameter alpha"
-           },
-           "scale": {
-             "type": "number",
-             "description": "scale parameter theta"
-           }
-        },
-        "required": ["type", "min", "max", "shape", "scale"]
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/distributions/lognormal_distribution.json b/engine/src/Utils/Property/schemas/distributions/lognormal_distribution.json
deleted file mode 100644
index 90ea615d..00000000
--- a/engine/src/Utils/Property/schemas/distributions/lognormal_distribution.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "LogNormalDistribution",
-  "allOf": [
-    {
-      "$ref": "base_distribution.json"
-    },
-    {
-      "if": {
-        "properties": {
-          "type": {
-            "enum": [
-              "lognormal", "LogNormal", "LogNormalDistribution", "lognormal_distribution",
-              "LogNormal_Distribution", "lognormal-distribution", "LogNormal-Distribution",
-              "logNormalDistribution", "lognormaldistribution"
-            ]
-          }
-        }
-      },
-      "then": {
-        "properties": {
-          "mu": {
-            "type": "number",
-            "description": "The mean of the natural logarithm of the distribution"
-          },
-          "sigma": {
-            "type": "number",
-            "description": "The standard deviation of the natural logarithm of the distribution"
-          }
-        },
-        "required": ["type", "min", "max", "mu", "sigma"]
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/distributions/normal_distrubution.json b/engine/src/Utils/Property/schemas/distributions/normal_distrubution.json
deleted file mode 100644
index e4b3be83..00000000
--- a/engine/src/Utils/Property/schemas/distributions/normal_distrubution.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "NormalDistribution",
-  "allOf": [
-    {
-      "$ref": "base_distribution.json"
-    },
-    {
-      "if": {
-        "properties": {
-          "type": {
-            "enum": [
-              "normal", "Normal", "NormalDistribution", "normal_distribution",
-              "Normal_Distribution", "normal-distribution", "Normal-Distribution",
-              "normalDistribution", "normaldistribution"
-            ]
-          }
-        }
-      },
-      "then": {
-        "properties": {
-          "mean": {
-            "type": "number",
-            "description": "The mean value of the distribution"
-          },
-          "stddev": {
-            "type": "number",
-            "description": "The standard deviation of the distribution"
-          }
-        },
-        "required": ["type", "min", "max", "mean", "stddev"]
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/engine/src/Utils/Property/schemas/distributions/uniform_distribution.json b/engine/src/Utils/Property/schemas/distributions/uniform_distribution.json
deleted file mode 100644
index c7d19757..00000000
--- a/engine/src/Utils/Property/schemas/distributions/uniform_distribution.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "title": "UniformDistribution",
-  "allOf": [
-    {
-      "$ref": "base_distribution.json"
-    },
-    {
-      "if": {
-        "properties": {
-          "type": {
-            "enum": [
-              "uniform", "Uniform", "UniformDistribution", "uniform_distribution",
-              "Uniform_Distribution", "uniform-distribution", "Uniform-Distribution",
-              "uniformDistribution", "uniformdistribution"]
-          }
-        }
-      },
-      "then": {
-        "required": ["type", "min", "max"]
-      }
-    }
-  ]
-}
\ No newline at end of file
diff --git a/engine/src/Utils/PropertyInterpretation/DistributionInterpreter.cpp b/engine/src/Utils/PropertyInterpretation/DistributionInterpreter.cpp
new file mode 100644
index 00000000..479a44cc
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/DistributionInterpreter.cpp
@@ -0,0 +1,65 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#include "DistributionInterpreter.h"
+
+#include <Stochastics/StochasticsInterface.h>
+
+#include <algorithm>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+double sample(StochasticsInterface &stochastics, const ExponentialDistribution &distribution)
+{
+  return std::clamp(
+      stochastics.GetExponentialDistributed(
+          distribution.expectedValue != 0.0 ? 1.0 / distribution.expectedValue : 0.0),
+      distribution.lowerLimit,
+      distribution.upperLimit);
+}
+
+double sample(StochasticsInterface &stochastics, const GammaDistribution &distribution)
+{
+  return std::clamp(
+      stochastics.GetGammaDistributed(
+          distribution.expectedValue, std::sqrt(std::abs(distribution.variance))),
+      distribution.lowerLimit,
+      distribution.upperLimit);
+}
+
+double sample(StochasticsInterface &stochastics, const LogNormalDistribution &distribution)
+{
+  return std::clamp(
+      stochastics.GetLogNormalDistributed(
+          distribution.expectedValue, std::sqrt(std::abs(distribution.variance))),
+      distribution.lowerLimit,
+      distribution.upperLimit);
+}
+
+double sample(StochasticsInterface &stochastics, const NormalDistribution &distribution)
+{
+  return std::clamp(
+      stochastics.GetNormalDistributed(
+          distribution.expectedValue, std::sqrt(std::abs(distribution.variance))),
+      distribution.lowerLimit,
+      distribution.upperLimit);
+}
+
+double sample(StochasticsInterface &stochastics, const UniformDistribution &distribution)
+{
+  return std::clamp(
+      stochastics.GetUniformDistributed(
+          distribution.lowerLimit, distribution.upperLimit),
+      distribution.lowerLimit,
+      distribution.upperLimit);
+}
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
\ No newline at end of file
diff --git a/engine/src/Utils/PropertyInterpretation/DistributionInterpreter.h b/engine/src/Utils/PropertyInterpretation/DistributionInterpreter.h
new file mode 100644
index 00000000..7b16b836
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/DistributionInterpreter.h
@@ -0,0 +1,104 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#pragma once
+
+#include "Utils/Logger.h"
+#include "Utils/PropertyInterpretation/Distributions.h"
+#include "Utils/PropertyInterpretation/JsonInterpreter.h"
+
+class StochasticsInterface;
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Sample from an exponential distribution
+/// @param stochastics  Stochastics interface
+/// @param distribution Definition of the distribution
+/// @return sampled value
+double sample(StochasticsInterface &stochastics, const ExponentialDistribution &distribution);
+
+/// Sample from a gamma distribution
+/// @param stochastics  Stochastics interface
+/// @param distribution Definition of the distribution
+/// @return sampled value
+double sample(StochasticsInterface &stochastics, const GammaDistribution &distribution);
+
+/// Sample from a log normal distribution
+/// @param stochastics  Stochastics interface
+/// @param distribution Definition of the distribution
+/// @return sampled value
+double sample(StochasticsInterface &stochastics, const LogNormalDistribution &distribution);
+
+/// Sample from a normal distribution
+/// @param stochastics  Stochastics interface
+/// @param distribution Definition of the distribution
+/// @return sampled value
+double sample(StochasticsInterface &stochastics, const NormalDistribution &distribution);
+
+/// Sample from a uniform distribution
+/// @param stochastics  Stochastics interface
+/// @param distribution Definition of the distribution
+/// @return sampled value
+double sample(StochasticsInterface &stochastics, const UniformDistribution &distribution);
+
+/// Interpreter for distributions
+/// @tparam Distribution Wrapped distribution type
+template <typename Distribution>
+struct DistributionInterpreter : public JsonInterpreter<Distribution>
+{
+  using JsonInterpreter<Distribution>::value_;  ///!< Internal representation
+
+  /// Binds Interpreter to stochastics interface
+  /// @param stochastics Stochastics interface
+  constexpr DistributionInterpreter(StochasticsInterface &stochastics) noexcept
+      : stochastics_{stochastics}
+  {
+  }
+
+  /// Sample from the distribution
+  /// @return string interpretation of the internal representation, empty if json interpretation failed
+  std::string to_string()
+  {
+    if (value_)
+    {
+      return std::to_string(sample(stochastics_, *value_));
+    }
+    Logger::Error("DistributionInterpreter: No distribution value available.");
+    return "";
+  }
+
+  StochasticsInterface &stochastics_;
+};
+
+/// Interpret a json object using a custom interpreter
+/// @note Specialize for custom behavor
+/// @tparam T          Type of the interpreter
+/// @tparam Payload    Type of the payload
+/// @param interpreter Interpreter to interpret the payload
+/// @param payload     Payload to interpret
+template <typename T, typename Payload>
+void interpret(DistributionInterpreter<T> &interpreter, const Payload &payload)
+{
+  interpreter.interpret(payload);
+}
+
+/// Convert internal representation to string
+/// @note Specialize for custom behavor
+/// @tparam T          Type of the interpreter
+/// @param interpreter Interpreter to convert to string
+/// @return            string interpretation of the internal representation
+template <typename T>
+std::string to_string(DistributionInterpreter<T> &interpreter)
+{
+  return interpreter.to_string();
+}
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
\ No newline at end of file
diff --git a/engine/src/Utils/Property/Distributions.h b/engine/src/Utils/PropertyInterpretation/Distributions.h
similarity index 63%
rename from engine/src/Utils/Property/Distributions.h
rename to engine/src/Utils/PropertyInterpretation/Distributions.h
index 75b675a3..c4919f77 100644
--- a/engine/src/Utils/Property/Distributions.h
+++ b/engine/src/Utils/PropertyInterpretation/Distributions.h
@@ -10,9 +10,8 @@
 
 #pragma once
 
-#include "Distribution/BaseDistribution.h"
-#include "Distribution/ExponentialDistribution.h"
-#include "Distribution/GammaDistribution.h"
-#include "Distribution/LogNormalDistribution.h"
-#include "Distribution/NormalDistribution.h"
-#include "Distribution/UniformDistribution.h"
+#include "Distributions/ExponentialDistribution.h"
+#include "Distributions/GammaDistribution.h"
+#include "Distributions/LogNormalDistribution.h"
+#include "Distributions/NormalDistribution.h"
+#include "Distributions/UniformDistribution.h"
diff --git a/engine/src/Utils/PropertyInterpretation/Distributions/ExponentialDistribution.h b/engine/src/Utils/PropertyInterpretation/Distributions/ExponentialDistribution.h
new file mode 100644
index 00000000..7605671f
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/Distributions/ExponentialDistribution.h
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Exponential distribution
+struct ExponentialDistribution
+{
+  double lowerLimit;     ///!< Lower limit
+  double upperLimit;     ///!< Upper limit
+  double expectedValue;  ///!< Reciprocal of the rate parameter lambda
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ExponentialDistribution,
+                                   lowerLimit,
+                                   upperLimit,
+                                   expectedValue);
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
diff --git a/engine/src/Utils/PropertyInterpretation/Distributions/GammaDistribution.h b/engine/src/Utils/PropertyInterpretation/Distributions/GammaDistribution.h
new file mode 100644
index 00000000..1e382fe7
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/Distributions/GammaDistribution.h
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Gamma distribution
+///
+/// The gamma distribution is a two-parameter family of continuous probability distributions.
+/// It is characterized by a shape parameter (alpha) and a rate parameter (beta).
+/// The expected value and variance of the gamma distribution are related to these parameters as follows:
+/// - Expected Value = alpha / beta
+/// - Variance = alpha / beta^2
+struct GammaDistribution
+{
+  double lowerLimit;     ///!< Lower limit
+  double upperLimit;     ///!< Upper limit
+  double expectedValue;  ///!< Expected value (mean) of the distribution, calculated as alpha / beta
+  double variance;       ///!< Variance of the distribution, calculated as alpha / beta^2
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(GammaDistribution,
+                                   lowerLimit,
+                                   upperLimit,
+                                   expectedValue,
+                                   variance);
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
diff --git a/engine/src/Utils/PropertyInterpretation/Distributions/LogNormalDistribution.h b/engine/src/Utils/PropertyInterpretation/Distributions/LogNormalDistribution.h
new file mode 100644
index 00000000..1aca4fc4
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/Distributions/LogNormalDistribution.h
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Normal distribution
+struct LogNormalDistribution
+{
+  double lowerLimit;     ///!< Lower limit
+  double upperLimit;     ///!< Upper limit
+  double expectedValue;  ///!< Expected value
+  double variance;       ///!< Variance (square of standard deviation)
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LogNormalDistribution,
+                                   lowerLimit,
+                                   upperLimit,
+                                   expectedValue,
+                                   variance);
+
+}  // namespace OpenScenarioEngine::v1_3
\ No newline at end of file
diff --git a/engine/src/Utils/PropertyInterpretation/Distributions/NormalDistribution.h b/engine/src/Utils/PropertyInterpretation/Distributions/NormalDistribution.h
new file mode 100644
index 00000000..d2a572f4
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/Distributions/NormalDistribution.h
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Normal distribution
+struct NormalDistribution
+{
+  double lowerLimit;     ///!< Lower limit
+  double upperLimit;     ///!< Upper limit
+  double expectedValue;  ///!< Expected value
+  double variance;       ///!< Variance (square of standard deviation)
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(NormalDistribution,
+                                   lowerLimit,
+                                   upperLimit,
+                                   expectedValue,
+                                   variance);
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
diff --git a/engine/src/Utils/Property/Distribution/UniformDistribution.h b/engine/src/Utils/PropertyInterpretation/Distributions/UniformDistribution.h
similarity index 56%
rename from engine/src/Utils/Property/Distribution/UniformDistribution.h
rename to engine/src/Utils/PropertyInterpretation/Distributions/UniformDistribution.h
index f0c465fd..05b4b145 100644
--- a/engine/src/Utils/Property/Distribution/UniformDistribution.h
+++ b/engine/src/Utils/PropertyInterpretation/Distributions/UniformDistribution.h
@@ -12,17 +12,18 @@
 
 #include <nlohmann/json.hpp>
 
-#include "BaseDistribution.h"
-
-namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
 {
 
-/// @brief Uniform distribution
-struct UniformDistribution : public BaseDistribution
+/// Uniform distribution
+struct UniformDistribution
 {
-  // No additional fields needed
+  double lowerLimit;  ///!< Lower limit
+  double upperLimit;  ///!< Upper limit
 };
 
-NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UniformDistribution, type, min, max);
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(UniformDistribution,
+                                   lowerLimit,
+                                   upperLimit);
 
-}  // namespace OpenScenarioEngine::v1_3::Utils::Property::Distribution
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
diff --git a/engine/src/Utils/PropertyInterpretation/Interpreter.h b/engine/src/Utils/PropertyInterpretation/Interpreter.h
new file mode 100644
index 00000000..0df771c4
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/Interpreter.h
@@ -0,0 +1,123 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <nlohmann/json.hpp>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Type-erased base for custom interpreter
+class Interpreter
+{
+  /// Concept for custom interpreter
+  struct InterpreterConcept
+  {
+    /// Destructor
+    virtual ~InterpreterConcept() {}
+
+    /// Interpret json object and hide internal representation
+    /// @param j json object to interpret
+    virtual void do_interpret(const nlohmann::json &j) = 0;
+
+    /// Convert internal representation to string
+    virtual std::string do_to_string() = 0;
+
+    /// Clone the interpreter
+    virtual std::unique_ptr<InterpreterConcept> clone() const = 0;
+  };
+
+  /// Model for custom interpreter
+  /// @tparam InterpreterT Actual interpreter type which shall be type-erased
+  template <typename InterpreterT>
+  struct InterpreterModel : public InterpreterConcept
+  {
+    InterpreterModel(InterpreterT interpreter)
+        : interpreter_{std::move(interpreter)}
+    {
+    }
+
+    std::unique_ptr<InterpreterConcept> clone() const override
+    {
+      return std::make_unique<InterpreterModel>(*this);
+    }
+
+    /// Relay to free function parse, which must be implemented for each custom interpreter
+    /// @param j json object to interpret
+    void do_interpret(const nlohmann::json &j) override
+    {
+      interpret(interpreter_, j);
+    }
+
+    /// Relay to free function to_string, which must be implemented for each custom interpreter
+    /// @return string interpretation of the internal representation
+    std::string do_to_string() override
+    {
+      return to_string(interpreter_);
+    }
+
+    InterpreterT interpreter_;  ///!< Actual interpreter
+  };
+
+  /// Free function to interpret json object (injects interpret into the base class)
+  /// @param interpreter interpreter to interpret the json object
+  /// @param j json object to interpret
+  friend void interpret(Interpreter &interpreter, const nlohmann::json &j)
+  {
+    interpreter.pimpl_->do_interpret(j);
+  }
+
+  /// Free function to convert internal representation to string (injects to_string into the base class)
+  /// @param interpreter interpreter used to convert to string
+  /// @return string interpretation of the internal representation
+  friend std::string to_string(Interpreter &interpreter)
+  {
+    return interpreter.pimpl_->do_to_string();
+  }
+
+  std::unique_ptr<InterpreterConcept> pimpl_;  ///!< Type-erased interpreter
+
+public:
+  /// Constructor to create a type erased custom interpreter
+  /// @tparam InterpreterT Actual interpreter type
+  /// @param interpreter Actual interpreter
+  template <typename InterpreterT>
+  Interpreter(InterpreterT interpreter)
+      : pimpl_{std::make_unique<InterpreterModel<InterpreterT>>(std::move(interpreter))}
+  {
+  }
+
+  /// Copy constructor
+  /// @param other
+  Interpreter(const Interpreter &other)
+      : pimpl_{other.pimpl_->clone()}
+  {
+  }
+
+  /// Copy assignment operator
+  /// @param other
+  /// @return New interpreter
+  Interpreter &operator=(const Interpreter &other)
+  {
+    other.pimpl_->clone().swap(pimpl_);
+    return *this;
+  }
+
+  Interpreter(Interpreter &&) = default;             ///!< Move constructor
+  Interpreter &operator=(Interpreter &&) = default;  ///!< Move assignment operator
+};
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
diff --git a/engine/src/Utils/PropertyInterpretation/JsonInterpreter.h b/engine/src/Utils/PropertyInterpretation/JsonInterpreter.h
new file mode 100644
index 00000000..be2baa76
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/JsonInterpreter.h
@@ -0,0 +1,43 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#pragma once
+
+#include <nlohmann/json.hpp>
+#include <optional>
+#include <string>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Interpreter for json objects
+/// @tparam T Type for deserialization
+template <typename T>
+struct JsonInterpreter
+{
+  /// Interpret and store the json object
+  /// @note If the json object cannot be interpreted, the internal value will be std::nullopt
+  /// @param j json object to interpret
+  void interpret(const nlohmann::json &j)
+  {
+    try
+    {
+      value_ = j.get<T>();
+    }
+    catch (const nlohmann::json::out_of_range &)
+    {
+      value_ = std::nullopt;
+    }
+  }
+
+  std::optional<T> value_;
+};
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
\ No newline at end of file
diff --git a/engine/src/Utils/PropertyInterpretation/RawValueRelay.h b/engine/src/Utils/PropertyInterpretation/RawValueRelay.h
new file mode 100644
index 00000000..26246b48
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpretation/RawValueRelay.h
@@ -0,0 +1,43 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#pragma once
+
+#include <string>
+#include <utility>
+
+namespace OpenScenarioEngine::v1_3::PropertyInterpretation
+{
+
+/// Default interpreter if no custom interpreter is available or can be resolved
+struct RawValueRelay
+{
+  std::string value_;
+};
+
+/// Void operation needed for type-erased interpreter stage
+/// @tparam ...DontCare     Catches all parameters, if any
+/// @param raw_value_relay  Actual intepreter
+/// @param ...              ignored
+template <typename... DontCare>
+void interpret(const RawValueRelay &raw_value_relay, DontCare...)
+{
+  std::ignore = raw_value_relay;
+}
+
+/// Return the internal representation without doing anything
+/// @param raw_value_relay Actual interpreter
+/// @return stored string
+std::string to_string(const RawValueRelay &raw_value_relay)
+{
+  return raw_value_relay.value_;
+}
+
+}  // namespace OpenScenarioEngine::v1_3::PropertyInterpretation
\ No newline at end of file
diff --git a/engine/src/Utils/PropertyInterpreter.cpp b/engine/src/Utils/PropertyInterpreter.cpp
new file mode 100644
index 00000000..4071c912
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpreter.cpp
@@ -0,0 +1,71 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#include "PropertyInterpreter.h"
+
+#include <algorithm>
+
+#include "Utils/JsonParsing.h"
+#include "Utils/PropertyInterpretation/DistributionInterpreter.h"
+#include "Utils/PropertyInterpretation/RawValueRelay.h"
+
+namespace OpenScenarioEngine::v1_3
+{
+
+using namespace PropertyInterpretation;
+
+namespace detail
+{
+std::pair<std::string, nlohmann::json> GetTypeAndValue(const std::string &property_value)
+{
+  if (auto j = json::TryParse(property_value);
+      j.has_value() && j->contains("type") && j->contains("value"))
+  {
+    return {j->at("type").get<std::string>(), j->at("value")};
+  }
+  return {"", nlohmann::json{}};
+}
+
+}  // namespace detail
+
+void PropertyInterpreter::Init(
+    const std::map<std::string, std::string> &properties)
+{
+  interpreter_mapping_.clear();
+  for (const auto &[property_key, property_value] : properties)
+  {
+    const auto [json_type, json_value] = detail::GetTypeAndValue(property_value);
+
+    const auto interpreter_iter = interpreter_prototypes_.find(json_type);
+    auto [interpreter, _] = interpreter_mapping_.try_emplace(
+        property_key,
+        interpreter_iter != interpreter_prototypes_.end()
+            ? interpreter_iter->second
+            : RawValueRelay{property_value});
+    interpret(interpreter->second, json_value);
+  }
+}
+
+std::map<std::string, std::string> PropertyInterpreter::GenerateParameters(
+    const std::map<std::string, std::string> &key_replacements)
+{
+  std::map<std::string, std::string> parameters;
+  for (auto &[key, value] : interpreter_mapping_)
+  {
+    auto iter = key_replacements.find(key);
+    parameters.emplace(iter == std::end(key_replacements)
+                           ? key
+                           : iter->second,
+                       to_string(value));
+  }
+  return parameters;
+}
+
+}  // namespace OpenScenarioEngine::v1_3
diff --git a/engine/src/Utils/PropertyInterpreter.h b/engine/src/Utils/PropertyInterpreter.h
new file mode 100644
index 00000000..19105902
--- /dev/null
+++ b/engine/src/Utils/PropertyInterpreter.h
@@ -0,0 +1,53 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "Utils/PropertyInterpretation/Interpreter.h"
+
+namespace OpenScenarioEngine::v1_3
+{
+
+/// Utility class to interpret properties, e.g. custom json distribution definitions
+///
+/// This class can take a map of properties and interpret them using custom interpreters.
+/// Each property is interpreted by a custom interpreter based the value of the property.
+/// If a json object is found, the "type" field is used to determine the interpreter.
+/// In such a case, the "value" field is relayed to the interpreter for interpretation.
+/// If no interpreter is found or the json has no field "type" and "value", the property
+/// is treated as a raw value (= string).
+class PropertyInterpreter
+{
+public:
+  using Interpreters = std::map<std::string, PropertyInterpretation::Interpreter>;
+
+  /// Constructor to create a property interpreter
+  /// @param interpreter_prototypes Prototypes for custom interpreters
+  PropertyInterpreter(Interpreters &&interpreter_prototypes);
+
+  /// Initialize the property interpreter for a given set of properties
+  /// @param properties             Properties to interpret
+  void Init(const std::map<std::string, std::string> &properties);
+
+  /// Generate parameters from the interpreted properties, given at initialization
+  /// @note Might return different values at every call, e.g. for stochastic properties
+  /// @param key_replacements defines a mapping from original keys to new keys
+  /// @return interpreted parameter map
+  std::map<std::string, std::string> GenerateParameters(
+      const std::map<std::string, std::string> &key_replacements = {});
+
+private:
+  Interpreters interpreter_prototypes_;  ///!< Available custom interpreters
+  Interpreters interpreter_mapping_;     ///!< Property to interpreter relation
+};
+}  // namespace OpenScenarioEngine::v1_3
\ No newline at end of file
diff --git a/engine/tests/Utils/DistributionTest.cpp b/engine/tests/Utils/DistributionTest.cpp
new file mode 100644
index 00000000..51c4ce2b
--- /dev/null
+++ b/engine/tests/Utils/DistributionTest.cpp
@@ -0,0 +1,160 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "TestUtils/MockStochastics.h"
+#include "Utils/PropertyInterpretation/DistributionInterpreter.h"
+#include "Utils/PropertyInterpretation/Distributions.h"
+
+using namespace OpenScenarioEngine::v1_3;
+using namespace OpenScenarioEngine::v1_3::PropertyInterpretation;
+using namespace testing;
+
+class DistributionTest : public Test
+{
+protected:
+  MockStochastics stochastics;
+};
+
+struct DistributionTestParams
+{
+  double lowerLimit;
+  double upperLimit;
+  double sampledValue;
+  double expectedValue;
+};
+
+class ExponentialDistributionTest : public DistributionTest, public WithParamInterface<DistributionTestParams>
+{
+};
+
+TEST_P(ExponentialDistributionTest, GivenExponentialDistribution_WhenSampleIsCalled_ThenReturnsClampedValue)
+{
+  auto params = GetParam();
+  ExponentialDistribution distribution{params.lowerLimit, params.upperLimit, 2.0};
+  ON_CALL(stochastics, GetExponentialDistributed(1.0 / 2.0))
+      .WillByDefault(Return(params.sampledValue));
+
+  double result = sample(stochastics, distribution);
+
+  EXPECT_THAT(result, DoubleEq(params.expectedValue));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    ExponentialDistributionTests,
+    ExponentialDistributionTest,
+    ::testing::Values(
+        DistributionTestParams{0.0, 10.0, 5.0, 5.0},    // regular test
+        DistributionTestParams{0.0, 10.0, -1.0, 0.0},   // lowerLimit test
+        DistributionTestParams{0.0, 10.0, 15.0, 10.0},  // upperLimit test
+        DistributionTestParams{0.0, 10.0, 0.0, 0.0}     // division by zero test for expectedValue
+        ));
+
+class GammaDistributionTest : public DistributionTest, public WithParamInterface<DistributionTestParams>
+{
+};
+
+TEST_P(GammaDistributionTest, GivenGammaDistribution_WhenSampleIsCalled_ThenReturnsClampedValue)
+{
+  auto params = GetParam();
+  GammaDistribution distribution{params.lowerLimit, params.upperLimit, 2.0, 4.0};
+  ON_CALL(stochastics, GetGammaDistributed(2.0, std::sqrt(4.0)))
+      .WillByDefault(Return(params.sampledValue));
+
+  double result = sample(stochastics, distribution);
+
+  EXPECT_THAT(result, DoubleEq(params.expectedValue));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    GammaDistributionTests,
+    GammaDistributionTest,
+    ::testing::Values(
+        DistributionTestParams{0.0, 10.0, 5.0, 5.0},   // regular test
+        DistributionTestParams{0.0, 10.0, -1.0, 0.0},  // lowerLimit test
+        DistributionTestParams{0.0, 10.0, 15.0, 10.0}  // upperLimit test
+        ));
+
+class LogNormalDistributionTest : public DistributionTest, public WithParamInterface<DistributionTestParams>
+{
+};
+
+TEST_P(LogNormalDistributionTest, GivenLogNormalDistribution_WhenSampleIsCalled_ThenReturnsClampedValue)
+{
+  auto params = GetParam();
+  LogNormalDistribution distribution{params.lowerLimit, params.upperLimit, 2.0, 4.0};
+  ON_CALL(stochastics, GetLogNormalDistributed(2.0, std::sqrt(4.0)))
+      .WillByDefault(Return(params.sampledValue));
+
+  double result = sample(stochastics, distribution);
+
+  EXPECT_THAT(result, DoubleEq(params.expectedValue));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    LogNormalDistributionTests,
+    LogNormalDistributionTest,
+    ::testing::Values(
+        DistributionTestParams{0.0, 10.0, 5.0, 5.0},   // regular test
+        DistributionTestParams{0.0, 10.0, -1.0, 0.0},  // lowerLimit test
+        DistributionTestParams{0.0, 10.0, 15.0, 10.0}  // upperLimit test
+        ));
+
+class NormalDistributionTest : public DistributionTest, public WithParamInterface<DistributionTestParams>
+{
+};
+
+TEST_P(NormalDistributionTest, GivenNormalDistribution_WhenSampleIsCalled_ThenReturnsClampedValue)
+{
+  auto params = GetParam();
+  NormalDistribution distribution{params.lowerLimit, params.upperLimit, 2.0, 4.0};
+  ON_CALL(stochastics, GetNormalDistributed(2.0, std::sqrt(4.0)))
+      .WillByDefault(Return(params.sampledValue));
+
+  double result = sample(stochastics, distribution);
+
+  EXPECT_THAT(result, DoubleEq(params.expectedValue));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    NormalDistributionTests,
+    NormalDistributionTest,
+    ::testing::Values(
+        DistributionTestParams{0.0, 10.0, 5.0, 5.0},   // regular test
+        DistributionTestParams{0.0, 10.0, -1.0, 0.0},  // lowerLimit test
+        DistributionTestParams{0.0, 10.0, 15.0, 10.0}  // upperLimit test
+        ));
+
+class UniformDistributionTest : public DistributionTest, public WithParamInterface<DistributionTestParams>
+{
+};
+
+TEST_P(UniformDistributionTest, GivenUniformDistribution_WhenSampleIsCalled_ThenReturnsClampedValue)
+{
+  auto params = GetParam();
+  UniformDistribution distribution{params.lowerLimit, params.upperLimit};
+  ON_CALL(stochastics, GetUniformDistributed(params.lowerLimit, params.upperLimit))
+      .WillByDefault(Return(params.sampledValue));
+
+  double result = sample(stochastics, distribution);
+
+  EXPECT_THAT(result, DoubleEq(params.expectedValue));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    UniformDistributionTests,
+    UniformDistributionTest,
+    ::testing::Values(
+        DistributionTestParams{0.0, 10.0, 5.0, 5.0},   // regular test
+        DistributionTestParams{0.0, 10.0, -1.0, 0.0},  // lowerLimit test
+        DistributionTestParams{0.0, 10.0, 15.0, 10.0}  // upperLimit test
+        ));
diff --git a/engine/tests/Utils/JsonParserTest.cpp b/engine/tests/Utils/JsonParserTest.cpp
deleted file mode 100644
index 58ef5dff..00000000
--- a/engine/tests/Utils/JsonParserTest.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "Utils/Property/Distributions.h"
-#include "Utils/Property/JsonParser.h"
-
-using testing::DoubleEq;
-using testing::Eq;
-
-using namespace OpenScenarioEngine::v1_3::Utils::Property;
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringIsValidUniformDistribition_DeserializesToObject)
-{
-  auto uniformDistribution = ReadFromJson<Distribution::UniformDistribution>(
-      R"({"type": "uniform", "min": 1.2, "max": 3.4 })");
-
-  ASSERT_TRUE(uniformDistribution.has_value());
-  EXPECT_THAT(uniformDistribution->type, "uniform");
-  EXPECT_THAT(uniformDistribution->min, DoubleEq(1.2));
-  EXPECT_THAT(uniformDistribution->max, DoubleEq(3.4));
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringsAreEscaped_DeserializesToObject)
-{
-  auto uniformDistribution = ReadFromJson<Distribution::UniformDistribution>(
-      R"({&quot;type&quot;: &quot;uniform&quot;, &quot;min&quot;: 1.2, &quot;max&quot;: 3.4 })");
-
-  ASSERT_TRUE(uniformDistribution.has_value());
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringsAreInSingleQuotes_DeserializesToObject)
-{
-  auto uniformDistribution = ReadFromJson<Distribution::UniformDistribution>(
-      R"({'type': 'uniform', 'min': 1.2, 'max': 3.4 })");
-
-  ASSERT_TRUE(uniformDistribution.has_value());
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringIsValidNormalDistribition_DeserializesToObject)
-{
-  auto normalDistribution = ReadFromJson<Distribution::NormalDistribution>(
-      R"({"type": "normal", "min": 1.2, "max": 3.4 , "mean": 5.6, "stddev": 7.8 })");
-
-  ASSERT_TRUE(normalDistribution.has_value());
-  EXPECT_THAT(normalDistribution->type, "normal");
-  EXPECT_THAT(normalDistribution->min, DoubleEq(1.2));
-  EXPECT_THAT(normalDistribution->max, DoubleEq(3.4));
-  EXPECT_THAT(normalDistribution->mean, DoubleEq(5.6));
-  EXPECT_THAT(normalDistribution->stddev, DoubleEq(7.8));
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringIsValidLogNormalDistribition_DeserializesToObject)
-{
-  auto logNormalDistribution = ReadFromJson<Distribution::LogNormalDistribution>(
-      R"({"type": "lognormal", "min": 1.2, "max": 3.4, "mu": 5.6, "sigma": 7.8 })");
-
-  ASSERT_TRUE(logNormalDistribution.has_value());
-  EXPECT_THAT(logNormalDistribution->type, "lognormal");
-  EXPECT_THAT(logNormalDistribution->min, DoubleEq(1.2));
-  EXPECT_THAT(logNormalDistribution->max, DoubleEq(3.4));
-  EXPECT_THAT(logNormalDistribution->mu, DoubleEq(5.6));
-  EXPECT_THAT(logNormalDistribution->sigma, DoubleEq(7.8));
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringIsValidGammaDistribition_DeserializesToObject)
-{
-  auto gammaDistribution = ReadFromJson<Distribution::GammaDistribution>(
-      R"({"type": "gamma", "min": 1.2, "max": 3.4, "shape": 5.6, "scale": 7.8 })");
-
-  ASSERT_TRUE(gammaDistribution.has_value());
-  EXPECT_THAT(gammaDistribution->type, "gamma");
-  EXPECT_THAT(gammaDistribution->min, DoubleEq(1.2));
-  EXPECT_THAT(gammaDistribution->max, DoubleEq(3.4));
-  EXPECT_THAT(gammaDistribution->shape, DoubleEq(5.6));
-  EXPECT_THAT(gammaDistribution->scale, DoubleEq(7.8));
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringIsValidExponentialDistribition_DeserializesToObject)
-{
-  auto exponentialDistribution = ReadFromJson<Distribution::ExponentialDistribution>(
-      R"({"type": "exponential", "min": 1.2, "max": 3.4, "lamda": 5.6 })");
-
-  ASSERT_TRUE(exponentialDistribution.has_value());
-  EXPECT_THAT(exponentialDistribution->type, "exponential");
-  EXPECT_THAT(exponentialDistribution->min, DoubleEq(1.2));
-  EXPECT_THAT(exponentialDistribution->max, DoubleEq(3.4));
-  EXPECT_THAT(exponentialDistribution->lamda, DoubleEq(5.6));
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenStringIsInvalidJson_ReturnsEmptyOptional)
-{
-  EXPECT_THAT(ReadFromJson<Distribution::UniformDistribution>("this is not a json"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::NormalDistribution>("this is not a json"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::LogNormalDistribution>("this is not a json"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::GammaDistribution>("this is not a json"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::ExponentialDistribution>("this is not a json"), Eq(std::nullopt));
-}
-
-TEST(JsonParserTest, GivenPropertyString_WhenJsonDoesNotMatch_ReturnsEmptyOptional)
-{
-  EXPECT_THAT(ReadFromJson<Distribution::UniformDistribution>(R"({"unknown_field": "some_value"})"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::NormalDistribution>(R"({"unknown_field": "some_value"})"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::LogNormalDistribution>(R"({"unknown_field": "some_value"})"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::GammaDistribution>(R"({"unknown_field": "some_value"})"), Eq(std::nullopt));
-  EXPECT_THAT(ReadFromJson<Distribution::ExponentialDistribution>(R"({"unknown_field": "some_value"})"), Eq(std::nullopt));
-}
diff --git a/engine/tests/Utils/JsonParsingTest.cpp b/engine/tests/Utils/JsonParsingTest.cpp
new file mode 100644
index 00000000..d6287007
--- /dev/null
+++ b/engine/tests/Utils/JsonParsingTest.cpp
@@ -0,0 +1,80 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "Utils/JsonParsing.h"
+
+using testing::DoubleEq;
+using testing::Eq;
+
+using namespace OpenScenarioEngine::v1_3;
+
+struct DeserializationTestType
+{
+  int a;
+  double b;
+  std::string c;
+};
+
+NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(DeserializationTestType, a, b, c);
+
+TEST(JsonParsingTest, GivenPropertyString_WhenStringUsesDoubleQuotes_ThenReturnsObject)
+{
+  auto deserializationTestType = json::Deserialize<DeserializationTestType>(
+      R"({"a": 1, "b": 2.34, "c": "test"})");
+
+  ASSERT_TRUE(deserializationTestType.has_value());
+  EXPECT_THAT(deserializationTestType->a, Eq(1));
+  EXPECT_THAT(deserializationTestType->b, DoubleEq(2.34));
+  EXPECT_THAT(deserializationTestType->c, Eq("test"));
+}
+
+TEST(JsonParsingTest, GivenPropertyString_WhenStringUsesSingleQuotes_ThenReturnsObject)
+{
+  auto deserializationTestType = json::Deserialize<DeserializationTestType>(
+      R"({'a': 1, 'b': 2.34, 'c': 'test'})");
+
+  ASSERT_TRUE(deserializationTestType.has_value());
+  EXPECT_THAT(deserializationTestType->a, Eq(1));
+  EXPECT_THAT(deserializationTestType->b, DoubleEq(2.34));
+  EXPECT_THAT(deserializationTestType->c, Eq("test"));
+}
+
+TEST(JsonParsingTest, GivenPropertyString_WhenStringUsesExcapedXmlQuotes_ThenReturnsObject)
+{
+  auto deserializationTestType = json::Deserialize<DeserializationTestType>(
+      R"({&quot;a&quot;: 1, &quot;b&quot;: 2.34, &quot;c&quot;: &quot;test&quot;})");
+
+  ASSERT_TRUE(deserializationTestType.has_value());
+  EXPECT_THAT(deserializationTestType->a, Eq(1));
+  EXPECT_THAT(deserializationTestType->b, DoubleEq(2.34));
+  EXPECT_THAT(deserializationTestType->c, Eq("test"));
+}
+
+TEST(JsonParsingTest, GivenPropertyStringWithMoreFieldsThanNeccessary_WhenParsed_ReturnsObject)
+{
+  auto deserializationTestType = json::Deserialize<DeserializationTestType>(
+      R"({"a": 1, "b": 2.34, "c": "test", "d": 4})");
+
+  ASSERT_TRUE(deserializationTestType.has_value());
+  EXPECT_THAT(deserializationTestType->a, Eq(1));
+  EXPECT_THAT(deserializationTestType->b, DoubleEq(2.34));
+  EXPECT_THAT(deserializationTestType->c, Eq("test"));
+}
+
+TEST(JsonParsingTest, GivenPropertyString_WhenStringIsInvalid_ThenReturnsEmpty)
+{
+  auto deserializationTestType = json::Deserialize<DeserializationTestType>(
+      R"({a: 1, b: 2.34, c: test})");
+
+  ASSERT_FALSE(deserializationTestType.has_value());
+}
\ No newline at end of file
diff --git a/engine/tests/Utils/PropertyInterpreterTest.cpp b/engine/tests/Utils/PropertyInterpreterTest.cpp
new file mode 100644
index 00000000..c284ee42
--- /dev/null
+++ b/engine/tests/Utils/PropertyInterpreterTest.cpp
@@ -0,0 +1,142 @@
+/********************************************************************************
+ * Copyright (c) 2025 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "TestUtils/MockStochastics.h"
+#include "Utils/PropertyInterpretation/DistributionInterpreter.h"
+#include "Utils/PropertyInterpretation/Distributions.h"
+#include "Utils/PropertyInterpreter.h"
+
+using namespace OpenScenarioEngine::v1_3;
+using namespace OpenScenarioEngine::v1_3::PropertyInterpretation;
+using namespace testing;
+
+struct PropertyInterpreterTest : public Test
+{
+  void SetUp() override
+  {
+    ON_CALL(stochastics_, GetUniformDistributed(0, 1)).WillByDefault(Return(0.123));
+    // mean = 2, stddev = 3 ==> sqrt(variance = 9)
+    ON_CALL(stochastics_, GetNormalDistributed(2, 3)).WillByDefault(Return(2.345));
+  }
+
+  PropertyInterpreter::Interpreters GetInterpreterPrototypes()
+  {
+    return {
+        {"UniformDistribution", DistributionInterpreter<UniformDistribution>{stochastics_}},
+        {"NormalDistribution", DistributionInterpreter<NormalDistribution>{stochastics_}}};
+  }
+
+  NiceMock<MockStochastics> stochastics_;
+};
+
+TEST_F(PropertyInterpreterTest, GivenProperties_WhenCallingPropertyInterpreter_ThenInterpretsResults)
+{
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(
+      {{"prop0", R"({"type": "UniformDistribution", "value": {"lowerLimit": 0, "upperLimit": 1} })"},
+       {"prop1", R"({"type": "NormalDistribution", "value": {"lowerLimit": 0, "upperLimit": 10, "expectedValue": 2, "variance": 9} })"},
+       {"prop2", "RAW_PROPERTY"}});
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::SizeIs(3));
+  EXPECT_EQ(result["prop0"], "0.123000");
+  EXPECT_EQ(result["prop1"], "2.345000");
+  EXPECT_EQ(result["prop2"], "RAW_PROPERTY");
+}
+
+TEST_F(PropertyInterpreterTest, GivenPropertiesWithExtendedFields_WhenCalled_ThenInterpretsValues)
+{
+  std::map<std::string, std::string> test_properties{
+      {"extended", R"(
+            {
+                "filter": ["extra"],
+                "mapping": "custom_property",
+                "type": "UniformDistribution",
+                "value": {"lowerLimit": 0, "upperLimit": 1}
+            })"}};
+
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(test_properties);
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::SizeIs(1));
+  EXPECT_EQ(result["extended"], "0.123000");
+}
+
+TEST_F(PropertyInterpreterTest, GivenPropertyWithoutType_WhenCallingPropertyInterpreter_ThenReturnsRawValue)
+{
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(
+      {{"prop0", R"({"value": {"lowerLimit": 0, "upperLimit": 1} })"}});
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::SizeIs(1));
+  EXPECT_EQ(result["prop0"], R"({"value": {"lowerLimit": 0, "upperLimit": 1} })");
+}
+
+TEST_F(PropertyInterpreterTest, GivenPropertyWithoutValue_WhenCallingPropertyInterpreter_ThenReturnsRawValue)
+{
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(
+      {{"prop0", R"({"type": "UniformDistribution"})"}});
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::SizeIs(1));
+  EXPECT_EQ(result["prop0"], R"({"type": "UniformDistribution"})");
+}
+
+TEST_F(PropertyInterpreterTest, GivenInvalidJson_WhenCallingPropertyInterpreter_ThenReturnsRawValue)
+{
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(
+      {{"prop0", "invalid_json"}});
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::SizeIs(1));
+  EXPECT_EQ(result["prop0"], "invalid_json");
+}
+
+TEST_F(PropertyInterpreterTest, GivenUnsupportedType_WhenCallingPropertyInterpreter_ThenReturnsRawValue)
+{
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(
+      {{"prop0", R"({"type": "UnsupportedType", "value": {"lowerLimit": 0, "upperLimit": 1} })"}});
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::SizeIs(1));
+  EXPECT_EQ(result["prop0"], R"({"type": "UnsupportedType", "value": {"lowerLimit": 0, "upperLimit": 1} })");
+}
+
+TEST_F(PropertyInterpreterTest, GivenEmptyProperties_WhenCallingPropertyInterpreter_ThenReturnsEmptyResult)
+{
+  std::map<std::string, std::string> properties_under_test{};
+
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(properties_under_test);
+  auto result = property_interpreter.GenerateParameters();
+
+  ASSERT_THAT(result, testing::IsEmpty());
+}
+
+TEST_F(PropertyInterpreterTest, GivenKeyReplacements_WhenCallingPropertyInterpreter_ThenReturnsReplacedKeys)
+{
+  PropertyInterpreter property_interpreter(GetInterpreterPrototypes());
+  property_interpreter.Init(
+      {{"prop0", R"({"type": "UniformDistribution", "value": {"lowerLimit": 0, "upperLimit": 1} })"}});
+
+  std::map<std::string, std::string> key_replacements{{"prop0", "replaced_key"}};
+  auto result = property_interpreter.GenerateParameters(key_replacements);
+
+  ASSERT_THAT(result, testing::SizeIs(1));
+  EXPECT_EQ(result["replaced_key"], "0.123000");
+}
\ No newline at end of file
-- 
GitLab