From a1a80f70e5ca38b49cc66d3de4088444c7b4414d Mon Sep 17 00:00:00 2001 From: "DISGUISE-LONDON\\nathan.butt" Date: Tue, 7 Jul 2026 14:06:47 +0100 Subject: [PATCH 1/3] Integrated compile type checking for JSON structure data extraction. --- CMakeLists.txt | 2 +- include/opentrackio-cpp/OpenTrackIOHelper.h | 126 +++++++++-- .../opentrackio-cpp/OpenTrackIOProperties.h | 4 +- include/opentrackio-cpp/OpenTrackIOSample.h | 7 +- include/opentrackio-cpp/OpenTrackIOTypes.h | 203 +----------------- src/OpenTrackIOProperties.cpp | 123 +++++------ 6 files changed, 187 insertions(+), 278 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f706bb3..ff40b0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ set ( src/OpenTrackIOProperties.cpp src/OpenTrackIOSample.cpp + src/OpenTrackIOTypes.cpp ) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") @@ -78,7 +79,6 @@ target_include_directories( PUBLIC $ $ - ) target_link_libraries(${PROJECT_NAME} PUBLIC nlohmann_json::nlohmann_json) diff --git a/include/opentrackio-cpp/OpenTrackIOHelper.h b/include/opentrackio-cpp/OpenTrackIOHelper.h index 9769e61..80c63ea 100644 --- a/include/opentrackio-cpp/OpenTrackIOHelper.h +++ b/include/opentrackio-cpp/OpenTrackIOHelper.h @@ -14,8 +14,16 @@ #pragma once #include #include +#include +#include #include +#define TYPE_COVERSION_ASSERT(JsonConcept, CppType) \ + static_assert(JsonConcept, "Type is not compatible!"); + +#define TYPE_NON_COVERSION_ASSERT(JsonConcept, CppType) \ + static_assert(!JsonConcept, "Type is unexpectedly compatible!"); + namespace opentrackio { template @@ -32,6 +40,36 @@ namespace opentrackio { t.iris } -> std::convertible_to>; { t.zoom } -> std::convertible_to>; }; + + template + concept JsonBool = std::is_same_v; + template + concept JsonString = std::is_same_v; + template + concept JsonNumber = std::is_integral_v && !JsonBool; + template + concept JsonFloatDouble = std::is_floating_point_v && !JsonNumber; + + // Basic type conversion checks to ensure compatability with json library. + TYPE_COVERSION_ASSERT(JsonBool, bool); + TYPE_NON_COVERSION_ASSERT(JsonBool, int); + TYPE_NON_COVERSION_ASSERT(JsonBool, short); + TYPE_NON_COVERSION_ASSERT(JsonBool, long); + + TYPE_COVERSION_ASSERT(JsonString, std::string); + TYPE_NON_COVERSION_ASSERT(JsonString, const char*); + + TYPE_COVERSION_ASSERT(JsonNumber, int); + TYPE_COVERSION_ASSERT(JsonNumber, long); + TYPE_COVERSION_ASSERT(JsonNumber, short); + TYPE_NON_COVERSION_ASSERT(JsonNumber, float); + TYPE_NON_COVERSION_ASSERT(JsonNumber, double); + + TYPE_COVERSION_ASSERT(JsonFloatDouble, float); + TYPE_COVERSION_ASSERT(JsonFloatDouble, double); + TYPE_NON_COVERSION_ASSERT(JsonFloatDouble, int); + TYPE_NON_COVERSION_ASSERT(JsonFloatDouble, long); + TYPE_NON_COVERSION_ASSERT(JsonFloatDouble, short); class OpenTrackIOHelpers { @@ -83,10 +121,36 @@ namespace opentrackio template static void assignField(nlohmann::json &json, std::string_view fieldStr, std::optional &field, - std::string_view typeStr, std::vector &errors) + std::vector &errors) { if (json.contains(fieldStr)) { + if (!checkJsonTypeMatch(json[fieldStr], fieldStr, errors)) + return; + + getFieldFromJson(json[fieldStr], field); + json.erase(fieldStr); + } + } + + template + static void assignFieldArray(nlohmann::json& json, std::string_view fieldStr, std::optional>& field, + std::vector& errors) + { + if (json.contains(fieldStr)) + { + if (!json[fieldStr].is_array()) + { + errors.emplace_back(std::format("field: {0} isn't an array:", fieldStr)); + return; + } + + for (const auto& jsonValue : json[fieldStr]) + { + if (!checkJsonTypeMatch(jsonValue, fieldStr, errors)) + return; + } + getFieldFromJson(json[fieldStr], field); json.erase(fieldStr); } @@ -94,7 +158,7 @@ namespace opentrackio template static void assignField(nlohmann::json &json, std::string_view fieldStr, std::optional &field, - std::string_view typeStr, std::vector &errors) + std::vector &errors) { if (!json.contains(fieldStr)) { @@ -104,9 +168,9 @@ namespace opentrackio field = T{}; auto &encoderJson = json[fieldStr]; - assignField(encoderJson, "focus", field->focus, typeStr, errors); - assignField(encoderJson, "iris", field->iris, typeStr, errors); - assignField(encoderJson, "zoom", field->zoom, typeStr, errors); + assignField(encoderJson, "focus", field->focus, errors); + assignField(encoderJson, "iris", field->iris, errors); + assignField(encoderJson, "zoom", field->zoom, errors); if (!(field->focus.has_value() && field->iris.has_value() && field->zoom.has_value())) { @@ -140,23 +204,47 @@ namespace opentrackio json.erase(fieldStr); } } - }; - template<> - inline void OpenTrackIOHelpers::assignField>(nlohmann::json &json, std::string_view fieldStr, - std::optional> &field, - std::string_view typeStr, std::vector &errors) - { - if (!json.contains(fieldStr) || !json[fieldStr].is_array()) + static inline void assignStringArray(nlohmann::json& json, std::string_view fieldStr, + std::optional>& field, + std::vector& errors) { - field = std::nullopt; - return; + if (!json.contains(fieldStr) || !json[fieldStr].is_array()) + { + field = std::nullopt; + return; + } + + std::vector vec{}; + iterateJsonArrayAndPopulateVector(json[fieldStr], vec); + + field = std::move(vec); + json.erase(fieldStr); } - std::vector vec{}; - iterateJsonArrayAndPopulateVector(json[fieldStr], vec); + private: + template + static inline constexpr nlohmann::json::value_t getJsonTypeValue() + requires JsonBool || JsonFloatDouble || JsonNumber || JsonString + { + if constexpr (JsonString) { return nlohmann::json::value_t::string; } + else if constexpr (JsonBool) { return nlohmann::json::value_t::boolean; } + else if constexpr (JsonFloatDouble) { return nlohmann::json::value_t::number_float; } + else if constexpr (JsonNumber) { return nlohmann::json::value_t::number_unsigned; } + } + + template + static inline bool constexpr checkJsonTypeMatch(const nlohmann::json& json, const std::string_view& fieldStr, std::vector& errors) + { + nlohmann::json::value_t cppToJsonType = getJsonTypeValue(); + if (json.type() != cppToJsonType) + { + errors.emplace_back(std::format("field: {0} of type {1} isn't compatible with {2}.", fieldStr, json.type_name(), typeid(FieldT).name())); + return false; + } + + return true; + } - field = std::move(vec); - json.erase(fieldStr); - } + }; } // namespace opentrackio diff --git a/include/opentrackio-cpp/OpenTrackIOProperties.h b/include/opentrackio-cpp/OpenTrackIOProperties.h index c66d858..29b66bd 100644 --- a/include/opentrackio-cpp/OpenTrackIOProperties.h +++ b/include/opentrackio-cpp/OpenTrackIOProperties.h @@ -315,8 +315,8 @@ namespace opentrackio::opentrackioproperties struct SourceNumber { /** - * Number that identifies the index of the stream from a source from which data is being transported. - * This is most important in the case where a source is producing multiple streams of samples. */ + * Number that identifies the index of the stream from a source from which data is being transported. + * This is most important in the case where a source is producing multiple streams of samples. */ uint32_t value; static std::optional parse(nlohmann::json& json, std::vector& errors); diff --git a/include/opentrackio-cpp/OpenTrackIOSample.h b/include/opentrackio-cpp/OpenTrackIOSample.h index cb6f76f..559a1f7 100644 --- a/include/opentrackio-cpp/OpenTrackIOSample.h +++ b/include/opentrackio-cpp/OpenTrackIOSample.h @@ -38,10 +38,11 @@ namespace opentrackio bool initialise(const nlohmann::json& json); bool initialise(const std::string_view jsonString); bool initialise(std::span cbor); - const std::vector& getErrors() { return m_errorMessages; }; - const std::vector& getWarnings() { return m_warningMessages; }; + + const std::vector& getErrors() const { return m_errorMessages; }; + const std::vector& getWarnings() const { return m_warningMessages; }; const nlohmann::json& getJson(); - + private: void generateJson(); void parseCameraToJson(nlohmann::json& baseJson); diff --git a/include/opentrackio-cpp/OpenTrackIOTypes.h b/include/opentrackio-cpp/OpenTrackIOTypes.h index ff29968..1b0b025 100644 --- a/include/opentrackio-cpp/OpenTrackIOTypes.h +++ b/include/opentrackio-cpp/OpenTrackIOTypes.h @@ -17,7 +17,6 @@ #include #include #include -#include "opentrackio-cpp/OpenTrackIOHelper.h" namespace opentrackio::opentrackiotypes { @@ -35,31 +34,7 @@ namespace opentrackio::opentrackiotypes static std::optional parse(nlohmann::json& json, std::string_view fieldStr, - std::vector& errors) - { - const auto& rationalJson = json[fieldStr]; - - uint32_t num; - uint32_t denom; - if (!rationalJson.contains("num") || !rationalJson.contains("denom")) - { - errors.emplace_back(std::format("Key: {} is missing numerator or denominator field.", fieldStr)); - return std::nullopt; - } - - if (!rationalJson.at("num").is_number_unsigned() || !rationalJson.at("denom").is_number_unsigned()) - { - errors. - emplace_back(std::format("Key: {} numerator or denominator field isn't of type: unsigned integer", - fieldStr)); - return std::nullopt; - } - - OpenTrackIOHelpers::getFieldFromJson(rationalJson["num"], num); - OpenTrackIOHelpers::getFieldFromJson(rationalJson["denom"], denom); - - return Rational(num, denom); - } + std::vector& errors); }; struct Vector3 @@ -78,31 +53,7 @@ namespace opentrackio::opentrackiotypes static std::optional parse(nlohmann::json& json, std::string_view fieldStr, - std::vector& errors) - { - const auto& vecJson = json[fieldStr]; - - Vector3 vec{}; - if (!vecJson.contains("x") || !vecJson.contains("y") || !vecJson.contains("z")) - { - errors.emplace_back(std::format("Key: {} Vector3 is missing required fields", fieldStr)); - return std::nullopt; - } - - if (!vecJson.at("x").is_number() || - !vecJson.at("y").is_number() || - !vecJson.at("z").is_number()) - { - errors.emplace_back(std::format("Key: {} Vector3 fields aren't of type: double", fieldStr)); - return std::nullopt; - } - - OpenTrackIOHelpers::getFieldFromJson(vecJson["x"],vec.x); - OpenTrackIOHelpers::getFieldFromJson(vecJson["y"], vec.y); - OpenTrackIOHelpers::getFieldFromJson(vecJson["z"], vec.z); - - return vec; - } + std::vector& errors); }; struct Rotation @@ -121,31 +72,7 @@ namespace opentrackio::opentrackiotypes static std::optional parse(nlohmann::json& json, std::string_view fieldStr, - std::vector& errors) - { - const auto& rotJson = json[fieldStr]; - - Rotation rot{}; - if (!rotJson.contains("pan") || !rotJson.contains("tilt") || !rotJson.contains("roll")) - { - errors.emplace_back(std::format("Key: {} Rotation is missing required fields", fieldStr)); - return std::nullopt; - } - - if (!rotJson.at("pan").is_number() || - !rotJson.at("tilt").is_number() || - !rotJson.at("roll").is_number()) - { - errors.emplace_back(std::format("Key: {} Rotation fields aren't of type: double", fieldStr)); - return std::nullopt; - } - - OpenTrackIOHelpers::getFieldFromJson(rotJson["tilt"], rot.tilt); - OpenTrackIOHelpers::getFieldFromJson(rotJson["pan"], rot.pan); - OpenTrackIOHelpers::getFieldFromJson(rotJson["roll"], rot.roll); - - return rot; - } + std::vector& errors); }; struct Timecode @@ -178,44 +105,7 @@ namespace opentrackio::opentrackiotypes static std::optional parse(nlohmann::json& json, std::string_view fieldStr, - std::vector& errors) - { - auto& tcJson = json[fieldStr]; - - std::optional hours = std::nullopt; - std::optional minutes = std::nullopt; - std::optional seconds = std::nullopt; - std::optional frames = std::nullopt; - const std::optional frameRate = Rational::parse(tcJson, "frameRate", errors); - - OpenTrackIOHelpers::assignField(tcJson, "hours", hours, "uint8", errors); - OpenTrackIOHelpers::assignField(tcJson, "minutes", minutes, "uint8", errors); - OpenTrackIOHelpers::assignField(tcJson, "seconds", seconds, "uint8", errors); - OpenTrackIOHelpers::assignField(tcJson, "frames", frames, "uint8", errors); - - if (!hours.has_value() || !minutes.has_value() || !seconds.has_value() || !frames.has_value() || !frameRate. - has_value()) - { - errors.emplace_back("field: timing/timecode is missing required fields"); - return std::nullopt; - } - - std::optional subFrame; - OpenTrackIOHelpers::assignField(tcJson, "subFrame", subFrame, "uint32_t", errors); - - std::optional dropFrame; - OpenTrackIOHelpers::assignField(tcJson, "dropFrame", dropFrame, "boolean", errors); - - return Timecode{ - hours.value(), - minutes.value(), - seconds.value(), - frames.value(), - frameRate.value(), - subFrame, - dropFrame - }; - } + std::vector& errors); }; struct Timestamp @@ -232,28 +122,11 @@ namespace opentrackio::opentrackiotypes static std::optional parse(nlohmann::json& json, std::string_view fieldStr, - std::vector& errors) - { - auto& tsJson = json[fieldStr]; - - std::optional seconds = std::nullopt; - std::optional nanoseconds = std::nullopt; - - OpenTrackIOHelpers::assignField(tsJson, "seconds", seconds, "uint64", errors); - OpenTrackIOHelpers::assignField(tsJson, "nanoseconds", nanoseconds, "uint32", errors); - - if (!seconds.has_value() || !nanoseconds.has_value()) - { - errors.emplace_back("field: timestamp is missing required fields"); - return std::nullopt; - } - - return Timestamp(seconds.value(), nanoseconds.value()); - } + std::vector& errors); }; template - requires std::integral || std::floating_point + requires (std::is_arithmetic_v && !std::is_same_v) struct Dimensions { T width = 0; @@ -265,27 +138,10 @@ namespace opentrackio::opentrackiotypes height{h} { }; - - static std::optional > parse(nlohmann::json& json, - std::string_view fieldStr, - std::vector& errors) - { - auto& dimJson = json[fieldStr]; - - std::optional width = std::nullopt; - std::optional height = std::nullopt; - - OpenTrackIOHelpers::assignField(dimJson, "width", width, "double", errors); - OpenTrackIOHelpers::assignField(dimJson, "height", height, "double", errors); - - if (!width.has_value() || !height.has_value()) - { - errors.emplace_back(std::format("Key: {} dimensions is missing required fields", fieldStr)); - return std::nullopt; - } - - return Dimensions(width.value(), height.value()); - } + + static std::optional> parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors); }; struct Transform @@ -302,43 +158,6 @@ namespace opentrackio::opentrackiotypes { }; - static std::optional parse(nlohmann::json& json, std::vector& errors) - { - Transform tf{}; - - // Required Fields -------- - std::optional translation = std::nullopt; - std::optional rotation = std::nullopt; - - if (!json.contains("translation") || !json.contains("rotation")) - { - return std::nullopt; - } - - translation = Vector3::parse(json, "translation", errors); - json.erase("translation"); - - rotation = Rotation::parse(json, "rotation", errors); - json.erase("rotation"); - - if (!translation.has_value() || !rotation.has_value()) - { - return std::nullopt; - } - - tf.translation = translation.value(); - tf.rotation = rotation.value(); - - // Non-required fields ------ - if (json.contains("scale")) - { - tf.scale = Vector3::parse(json, "scale", errors); - json.erase("scale"); - } - - OpenTrackIOHelpers::assignField(json, "id", tf.id, "string", errors); - - return tf; - } + static std::optional parse(nlohmann::json& json, std::vector& errors); }; } // namespace opentrackio::opentrackiotypes diff --git a/src/OpenTrackIOProperties.cpp b/src/OpenTrackIOProperties.cpp index ee6c477..536d823 100644 --- a/src/OpenTrackIOProperties.cpp +++ b/src/OpenTrackIOProperties.cpp @@ -11,8 +11,9 @@ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "opentrackio-cpp/OpenTrackIOProperties.h" #include "opentrackio-cpp/OpenTrackIOHelper.h" +#include "opentrackio-cpp/OpenTrackIOProperties.h" + #include namespace opentrackio::opentrackioproperties @@ -53,11 +54,11 @@ namespace opentrackio::opentrackioproperties cameraJson.erase("anamorphicSqueeze"); } - OpenTrackIOHelpers::assignField(cameraJson, "firmwareVersion", cam.firmwareVersion, "string", errors); - OpenTrackIOHelpers::assignField(cameraJson, "label", cam.label, "string", errors); - OpenTrackIOHelpers::assignField(cameraJson, "make", cam.make, "string", errors); - OpenTrackIOHelpers::assignField(cameraJson, "model", cam.model, "string", errors); - OpenTrackIOHelpers::assignField(cameraJson, "serialNumber", cam.serialNumber, "string", errors); + OpenTrackIOHelpers::assignField(cameraJson, "firmwareVersion", cam.firmwareVersion, errors); + OpenTrackIOHelpers::assignField(cameraJson, "label", cam.label, errors); + OpenTrackIOHelpers::assignField(cameraJson, "make", cam.make, errors); + OpenTrackIOHelpers::assignField(cameraJson, "model", cam.model, errors); + OpenTrackIOHelpers::assignField(cameraJson, "serialNumber", cam.serialNumber, errors); if (cameraJson.contains("captureFrameRate")) { @@ -68,8 +69,8 @@ namespace opentrackio::opentrackioproperties const std::regex pattern{R"(^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)"}; OpenTrackIOHelpers::assignRegexField(cameraJson, "fdlLink", cam.fdlLink, pattern, errors); - OpenTrackIOHelpers::assignField(cameraJson, "isoSpeed", cam.isoSpeed, "uint32", errors); - OpenTrackIOHelpers::assignField(cameraJson, "shutterAngle", cam.shutterAngle, "double", errors); + OpenTrackIOHelpers::assignField(cameraJson, "isoSpeed", cam.isoSpeed, errors); + OpenTrackIOHelpers::assignField(cameraJson, "shutterAngle", cam.shutterAngle, errors); if (cam.shutterAngle.has_value() && cam.shutterAngle.value() > 360) { @@ -98,8 +99,8 @@ namespace opentrackio::opentrackioproperties std::optional numerator = std::nullopt; std::optional denominator = std::nullopt; - OpenTrackIOHelpers::assignField(durationJson, "num", numerator, "uint64", errors); - OpenTrackIOHelpers::assignField(durationJson, "denom", denominator, "uint64", errors); + OpenTrackIOHelpers::assignField(durationJson, "num", numerator, errors); + OpenTrackIOHelpers::assignField(durationJson, "denom", denominator, errors); if (!numerator.has_value() || !denominator.has_value()) { @@ -172,14 +173,14 @@ namespace opentrackio::opentrackioproperties if (json.contains("static") && json["static"].contains("lens")) { auto& lensJson = json["static"]["lens"]; - OpenTrackIOHelpers::assignField(lensJson, "firmwareVersion", lens.firmwareVersion, "string", errors); - OpenTrackIOHelpers::assignField(lensJson, "make", lens.make, "string", errors); - OpenTrackIOHelpers::assignField(lensJson, "model", lens.model, "string", errors); - OpenTrackIOHelpers::assignField(lensJson, "nominalFocalLength", lens.nominalFocalLength, "double", errors); - OpenTrackIOHelpers::assignField(lensJson, "serialNumber", lens.serialNumber, "string", errors); - OpenTrackIOHelpers::assignField(lensJson, "distortionOverscanMax", lens.distortionOverscanMax, "double", errors); - OpenTrackIOHelpers::assignField(lensJson, "undistortionOverscanMax", lens.undistortionOverscanMax, "double", errors); - OpenTrackIOHelpers::assignField(lensJson, "calibrationHistory", lens.calibrationHistory, "string", errors); + OpenTrackIOHelpers::assignField(lensJson, "firmwareVersion", lens.firmwareVersion, errors); + OpenTrackIOHelpers::assignField(lensJson, "make", lens.make, errors); + OpenTrackIOHelpers::assignField(lensJson, "model", lens.model, errors); + OpenTrackIOHelpers::assignField(lensJson, "nominalFocalLength", lens.nominalFocalLength, errors); + OpenTrackIOHelpers::assignField(lensJson, "serialNumber", lens.serialNumber, errors); + OpenTrackIOHelpers::assignField(lensJson, "distortionOverscanMax", lens.distortionOverscanMax, errors); + OpenTrackIOHelpers::assignField(lensJson, "undistortionOverscanMax", lens.undistortionOverscanMax, errors); + OpenTrackIOHelpers::assignStringArray(lensJson, "calibrationHistory", lens.calibrationHistory, errors); OpenTrackIOHelpers::clearFieldIfEmpty(json["static"], "lens"); } @@ -204,10 +205,10 @@ namespace opentrackio::opentrackioproperties std::optional overscan = std::nullopt; std::optional model = std::nullopt; - OpenTrackIOHelpers::assignField(dist, "radial", radial, "double", errors); - OpenTrackIOHelpers::assignField(dist, "tangential", tangential, "double", errors); - OpenTrackIOHelpers::assignField(dist, "model", model, "string", errors); - OpenTrackIOHelpers::assignField(dist, "overscan", overscan, "double", errors); + OpenTrackIOHelpers::assignFieldArray(dist, "radial", radial, errors); + OpenTrackIOHelpers::assignFieldArray(dist, "tangential", tangential, errors); + OpenTrackIOHelpers::assignField(dist, "model", model, errors); + OpenTrackIOHelpers::assignField(dist, "overscan", overscan, errors); if (radial.has_value()) { @@ -227,8 +228,8 @@ namespace opentrackio::opentrackioproperties std::optional x = std::nullopt; std::optional y = std::nullopt; - OpenTrackIOHelpers::assignField(lensJson["distortionOffset"], "x", x, "double", errors); - OpenTrackIOHelpers::assignField(lensJson["distortionOffset"], "y", y, "double", errors); + OpenTrackIOHelpers::assignField(lensJson["distortionOffset"], "x", x, errors); + OpenTrackIOHelpers::assignField(lensJson["distortionOffset"], "y", y, errors); if (x.has_value() && y.has_value()) { @@ -237,8 +238,8 @@ namespace opentrackio::opentrackioproperties lensJson.erase("distortionOffset"); } - OpenTrackIOHelpers::assignField(lensJson, "encoders", lens.encoders, "double", errors); - OpenTrackIOHelpers::assignField(lensJson, "entrancePupilOffset", lens.entrancePupilOffset, "double", errors); + OpenTrackIOHelpers::assignField(lensJson, "encoders", lens.encoders, errors); + OpenTrackIOHelpers::assignField(lensJson, "entrancePupilOffset", lens.entrancePupilOffset, errors); if (lensJson.contains("exposureFalloff")) { @@ -246,9 +247,9 @@ namespace opentrackio::opentrackioproperties std::optional a2 = std::nullopt; std::optional a3 = std::nullopt; - OpenTrackIOHelpers::assignField(lensJson["exposureFalloff"], "a1", a1, "double", errors); - OpenTrackIOHelpers::assignField(lensJson["exposureFalloff"], "a2", a2, "double", errors); - OpenTrackIOHelpers::assignField(lensJson["exposureFalloff"], "a3", a3, "double", errors); + OpenTrackIOHelpers::assignField(lensJson["exposureFalloff"], "a1", a1, errors); + OpenTrackIOHelpers::assignField(lensJson["exposureFalloff"], "a2", a2, errors); + OpenTrackIOHelpers::assignField(lensJson["exposureFalloff"], "a3", a3, errors); if (a1.has_value()) { @@ -257,9 +258,9 @@ namespace opentrackio::opentrackioproperties lensJson.erase("exposureFalloff"); } - OpenTrackIOHelpers::assignField(lensJson, "fStop", lens.fStop, "double", errors); - OpenTrackIOHelpers::assignField(lensJson, "pinholeFocalLength", lens.pinholeFocalLength, "double", errors); - OpenTrackIOHelpers::assignField(lensJson, "focusDistance", lens.focusDistance, "double", errors); + OpenTrackIOHelpers::assignField(lensJson, "fStop", lens.fStop, errors); + OpenTrackIOHelpers::assignField(lensJson, "pinholeFocalLength", lens.pinholeFocalLength, errors); + OpenTrackIOHelpers::assignField(lensJson, "focusDistance", lens.focusDistance, errors); if (lensJson.contains("calibrationHistory") && lensJson["calibrationHistory"].is_array()) { @@ -272,8 +273,8 @@ namespace opentrackio::opentrackioproperties std::optional x = std::nullopt; std::optional y = std::nullopt; - OpenTrackIOHelpers::assignField(lensJson["projectionOffset"], "x", x, "double", errors); - OpenTrackIOHelpers::assignField(lensJson["projectionOffset"], "y", y, "double", errors); + OpenTrackIOHelpers::assignField(lensJson["projectionOffset"], "x", x, errors); + OpenTrackIOHelpers::assignField(lensJson["projectionOffset"], "y", y, errors); if (x.has_value() && y.has_value()) { @@ -285,13 +286,13 @@ namespace opentrackio::opentrackioproperties if (lensJson.contains("rawEncoders")) { lens.rawEncoders = RawEncoders{}; - OpenTrackIOHelpers::assignField(lensJson["rawEncoders"], "focus", lens.rawEncoders->focus, "unit32", errors); - OpenTrackIOHelpers::assignField(lensJson["rawEncoders"], "iris", lens.rawEncoders->iris, "uint32", errors); - OpenTrackIOHelpers::assignField(lensJson["rawEncoders"], "zoom", lens.rawEncoders->zoom, "uint32", errors); + OpenTrackIOHelpers::assignField(lensJson["rawEncoders"], "focus", lens.rawEncoders->focus, errors); + OpenTrackIOHelpers::assignField(lensJson["rawEncoders"], "iris", lens.rawEncoders->iris, errors); + OpenTrackIOHelpers::assignField(lensJson["rawEncoders"], "zoom", lens.rawEncoders->zoom, errors); lensJson.erase("rawEncoders"); } - OpenTrackIOHelpers::assignField(lensJson, "tStop", lens.tStop, "double", errors); + OpenTrackIOHelpers::assignField(lensJson, "tStop", lens.tStop, errors); OpenTrackIOHelpers::clearFieldIfEmpty(json, "lens"); } @@ -444,7 +445,7 @@ namespace opentrackio::opentrackioproperties } std::optional val; - OpenTrackIOHelpers::assignField(json, "sourceNumber", val, "uint32", errors); + OpenTrackIOHelpers::assignField(json, "sourceNumber", val, errors); if (!val.has_value()) { @@ -478,7 +479,7 @@ namespace opentrackio::opentrackioproperties } std::optional str; - OpenTrackIOHelpers::assignField(timingJson, "mode", str, "string", errors); + OpenTrackIOHelpers::assignField(timingJson, "mode", str, errors); if (str.has_value() && (str == "external" || str == "internal")) { timing.mode = str == "external" ? Mode::EXTERNAL : Mode::INTERNAL; @@ -502,7 +503,7 @@ namespace opentrackio::opentrackioproperties timingJson.erase("sampleTimestamp"); } - OpenTrackIOHelpers::assignField(timingJson, "sequenceNumber", timing.sequenceNumber, "uint16", errors); + OpenTrackIOHelpers::assignField(timingJson, "sequenceNumber", timing.sequenceNumber, errors); if (timingJson.contains("synchronization")) { @@ -603,9 +604,9 @@ namespace opentrackio::opentrackioproperties if (syncJson.contains("offsets")) { outSync.offsets = Synchronization::Offsets{}; - OpenTrackIOHelpers::assignField(syncJson["offsets"], "translation", outSync.offsets->translation, "double", errors); - OpenTrackIOHelpers::assignField(syncJson["offsets"], "rotation", outSync.offsets->rotation, "double", errors); - OpenTrackIOHelpers::assignField(syncJson["offsets"], "lensEncoders", outSync.offsets->lensEncoders, "double", errors); + OpenTrackIOHelpers::assignField(syncJson["offsets"], "translation", outSync.offsets->translation, errors); + OpenTrackIOHelpers::assignField(syncJson["offsets"], "rotation", outSync.offsets->rotation, errors); + OpenTrackIOHelpers::assignField(syncJson["offsets"], "lensEncoders", outSync.offsets->lensEncoders, errors); if (!outSync.offsets->translation.has_value() && !outSync.offsets->rotation.has_value() && !outSync.offsets->lensEncoders.has_value()) @@ -615,7 +616,7 @@ namespace opentrackio::opentrackioproperties syncJson.erase("offsets"); } - OpenTrackIOHelpers::assignField(syncJson, "present", outSync.present, "bool", errors); + OpenTrackIOHelpers::assignField(syncJson, "present", outSync.present, errors); if (syncJson.contains("ptp")) { @@ -636,7 +637,7 @@ namespace opentrackio::opentrackioproperties auto& ptpJson = json["ptp"]; std::optional profileStr; - OpenTrackIOHelpers::assignField(ptpJson, "profile", profileStr, "string", errors); + OpenTrackIOHelpers::assignField(ptpJson, "profile", profileStr, errors); bool successfullyAssignedProfileField = false; if (profileStr.has_value()) { @@ -672,7 +673,7 @@ namespace opentrackio::opentrackioproperties ptpJson.erase("profile"); std::optional domain; - OpenTrackIOHelpers::assignField(ptpJson, "domain", domain, "uint16", errors); + OpenTrackIOHelpers::assignField(ptpJson, "domain", domain, errors); if (!domain.has_value()) { errors.emplace_back("field: timing/synchronization/ptp/domain is required, however it is missing."); @@ -694,8 +695,8 @@ namespace opentrackio::opentrackioproperties std::optional priority1; std::optional priority2; constexpr auto leaderPrioritiesStr = "leaderPriorities"; - OpenTrackIOHelpers::assignField(ptpJson[leaderPrioritiesStr], "priority1", priority1, "uint8", errors); - OpenTrackIOHelpers::assignField(ptpJson[leaderPrioritiesStr], "priority2", priority2, "uint8", errors); + OpenTrackIOHelpers::assignField(ptpJson[leaderPrioritiesStr], "priority1", priority1, errors); + OpenTrackIOHelpers::assignField(ptpJson[leaderPrioritiesStr], "priority2", priority2, errors); if (!priority1.has_value() || !priority2.has_value()) { @@ -710,7 +711,7 @@ namespace opentrackio::opentrackioproperties }; std::optional leaderAccuracy; - OpenTrackIOHelpers::assignField(ptpJson, "leaderAccuracy", leaderAccuracy, "double", errors); + OpenTrackIOHelpers::assignField(ptpJson, "leaderAccuracy", leaderAccuracy, errors); if (!leaderAccuracy.has_value()) { errors.emplace_back("field: timing/synchronization/ptp/leaderAccuracy is required, however it is missing."); @@ -719,7 +720,7 @@ namespace opentrackio::opentrackioproperties outPtp.leaderAccuracy = leaderAccuracy.value(); std::optional meanPathDelay; - OpenTrackIOHelpers::assignField(ptpJson, "meanPathDelay", meanPathDelay, "double", errors); + OpenTrackIOHelpers::assignField(ptpJson, "meanPathDelay", meanPathDelay, errors); if (!meanPathDelay.has_value()) { errors.emplace_back("field: timing/synchronization/ptp/meanPathDelay is required, however it is missing."); @@ -727,10 +728,10 @@ namespace opentrackio::opentrackioproperties } outPtp.meanPathDelay = meanPathDelay.value(); - OpenTrackIOHelpers::assignField(ptpJson, "vlan", outPtp.vlan, "uint32", errors); + OpenTrackIOHelpers::assignField(ptpJson, "vlan", outPtp.vlan, errors); std::optional leaderTimeSourceStr; - OpenTrackIOHelpers::assignField(ptpJson, "leaderTimeSource", leaderTimeSourceStr, "string", errors); + OpenTrackIOHelpers::assignField(ptpJson, "leaderTimeSource", leaderTimeSourceStr, errors); if (leaderTimeSourceStr.has_value()) { if (leaderTimeSourceStr == "GNSS") @@ -763,10 +764,10 @@ namespace opentrackio::opentrackioproperties if (json.contains("static") && json["static"].contains("tracker")) { auto& tkrJson = json["static"]["tracker"]; - OpenTrackIOHelpers::assignField(tkrJson, "firmwareVersion", tkr.firmwareVersion, "string", errors); - OpenTrackIOHelpers::assignField(tkrJson, "make", tkr.make, "string", errors); - OpenTrackIOHelpers::assignField(tkrJson, "model", tkr.model, "string", errors); - OpenTrackIOHelpers::assignField(tkrJson, "serialNumber", tkr.serialNumber, "string", errors); + OpenTrackIOHelpers::assignField(tkrJson, "firmwareVersion", tkr.firmwareVersion, errors); + OpenTrackIOHelpers::assignField(tkrJson, "make", tkr.make, errors); + OpenTrackIOHelpers::assignField(tkrJson, "model", tkr.model, errors); + OpenTrackIOHelpers::assignField(tkrJson, "serialNumber", tkr.serialNumber, errors); OpenTrackIOHelpers::clearFieldIfEmpty(json["static"], "tracker"); } @@ -775,10 +776,10 @@ namespace opentrackio::opentrackioproperties if (json.contains("tracker")) { auto& tkrJson = json["tracker"]; - OpenTrackIOHelpers::assignField(tkrJson, "notes", tkr.notes, "string", errors); - OpenTrackIOHelpers::assignField(tkrJson, "recording", tkr.recording, "boolean", errors); - OpenTrackIOHelpers::assignField(tkrJson, "slate", tkr.slate, "string", errors); - OpenTrackIOHelpers::assignField(tkrJson, "status", tkr.status, "string", errors); + OpenTrackIOHelpers::assignField(tkrJson, "notes", tkr.notes, errors); + OpenTrackIOHelpers::assignField(tkrJson, "recording", tkr.recording, errors); + OpenTrackIOHelpers::assignField(tkrJson, "slate", tkr.slate, errors); + OpenTrackIOHelpers::assignField(tkrJson, "status", tkr.status, errors); OpenTrackIOHelpers::clearFieldIfEmpty(json, "tracker"); } From f2caa8a767dc0ec2dc1f34dbc6abbc2ccac0df11 Mon Sep 17 00:00:00 2001 From: "DISGUISE-LONDON\\nathan.butt" Date: Tue, 7 Jul 2026 14:12:11 +0100 Subject: [PATCH 2/3] Added missing source file. --- src/OpenTrackIOTypes.cpp | 243 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 src/OpenTrackIOTypes.cpp diff --git a/src/OpenTrackIOTypes.cpp b/src/OpenTrackIOTypes.cpp new file mode 100644 index 0000000..11711e8 --- /dev/null +++ b/src/OpenTrackIOTypes.cpp @@ -0,0 +1,243 @@ +/** + * Copyright 2025 Mo-Sys Engineering Ltd + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "opentrackio-cpp/OpenTrackIOHelper.h" +#include "opentrackio-cpp/OpenTrackIOTypes.h" + +namespace opentrackio::opentrackiotypes +{ + std::optional Rational::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + const auto& rationalJson = json[fieldStr]; + + uint32_t num; + uint32_t denom; + if (!rationalJson.contains("num") || !rationalJson.contains("denom")) + { + errors.emplace_back(std::format("Key: {} is missing numerator or denominator field.", fieldStr)); + return std::nullopt; + } + + if (!rationalJson.at("num").is_number_unsigned() || !rationalJson.at("denom").is_number_unsigned()) + { + errors.emplace_back(std::format("Key: {} numerator or denominator field isn't of type: unsigned integer", fieldStr)); + return std::nullopt; + } + + OpenTrackIOHelpers::getFieldFromJson(rationalJson["num"], num); + OpenTrackIOHelpers::getFieldFromJson(rationalJson["denom"], denom); + + return Rational(num, denom); + } + + std::optional Vector3::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + const auto& vecJson = json[fieldStr]; + + Vector3 vec{}; + if (!vecJson.contains("x") || !vecJson.contains("y") || !vecJson.contains("z")) + { + errors.emplace_back(std::format("Key: {} Vector3 is missing required fields", fieldStr)); + return std::nullopt; + } + + if (!vecJson.at("x").is_number() || + !vecJson.at("y").is_number() || + !vecJson.at("z").is_number()) + { + errors.emplace_back(std::format("Key: {} Vector3 fields aren't of type: double", fieldStr)); + return std::nullopt; + } + + OpenTrackIOHelpers::getFieldFromJson(vecJson["x"],vec.x); + OpenTrackIOHelpers::getFieldFromJson(vecJson["y"], vec.y); + OpenTrackIOHelpers::getFieldFromJson(vecJson["z"], vec.z); + + return vec; + } + + std::optional Rotation::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + const auto& rotJson = json[fieldStr]; + + std::optional rot = Rotation{}; + if (!rotJson.contains("pan") || !rotJson.contains("tilt") || !rotJson.contains("roll")) + { + errors.emplace_back(std::format("Key: {} Rotation is missing required fields", fieldStr)); + return std::nullopt; + } + + if (!rotJson.at("pan").is_number() || + !rotJson.at("tilt").is_number() || + !rotJson.at("roll").is_number()) + { + errors.emplace_back(std::format("Key: {} Rotation fields aren't of type: double", fieldStr)); + return std::nullopt; + } + + OpenTrackIOHelpers::getFieldFromJson(rotJson["tilt"], rot->tilt); + OpenTrackIOHelpers::getFieldFromJson(rotJson["pan"], rot->pan); + OpenTrackIOHelpers::getFieldFromJson(rotJson["roll"], rot->roll); + + return rot; + } + + std::optional Timecode::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + auto& tcJson = json[fieldStr]; + + std::optional hours = std::nullopt; + std::optional minutes = std::nullopt; + std::optional seconds = std::nullopt; + std::optional frames = std::nullopt; + const std::optional frameRate = Rational::parse(tcJson, "frameRate", errors); + + OpenTrackIOHelpers::assignField(tcJson, "hours", hours, errors); + OpenTrackIOHelpers::assignField(tcJson, "minutes", minutes, errors); + OpenTrackIOHelpers::assignField(tcJson, "seconds", seconds, errors); + OpenTrackIOHelpers::assignField(tcJson, "frames", frames, errors); + + if (!hours.has_value() || !minutes.has_value() || !seconds.has_value() || !frames.has_value() || !frameRate. + has_value()) + { + errors.emplace_back("field: timing/timecode is missing required fields"); + return std::nullopt; + } + + std::optional subFrame; + OpenTrackIOHelpers::assignField(tcJson, "subFrame", subFrame, errors); + + std::optional dropFrame; + OpenTrackIOHelpers::assignField(tcJson, "dropFrame", dropFrame, errors); + + return Timecode{ + hours.value(), + minutes.value(), + seconds.value(), + frames.value(), + frameRate.value(), + subFrame, + dropFrame + }; + } + + std::optional Timestamp::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + auto& tsJson = json[fieldStr]; + + std::optional seconds = std::nullopt; + std::optional nanoseconds = std::nullopt; + + OpenTrackIOHelpers::assignField(tsJson, "seconds", seconds, errors); + OpenTrackIOHelpers::assignField(tsJson, "nanoseconds", nanoseconds, errors); + + if (!seconds.has_value() || !nanoseconds.has_value()) + { + errors.emplace_back("field: timestamp is missing required fields"); + return std::nullopt; + } + + return Timestamp(seconds.value(), nanoseconds.value()); + } + + std::optional > Dimensions::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + auto& dimJson = json[fieldStr]; + + std::optional width = std::nullopt; + std::optional height = std::nullopt; + + OpenTrackIOHelpers::assignField(dimJson, "width", width, errors); + OpenTrackIOHelpers::assignField(dimJson, "height", height, errors); + + if (!width.has_value() || !height.has_value()) + { + errors.emplace_back(std::format("Key: {} dimensions is missing required fields", fieldStr)); + return std::nullopt; + } + + return Dimensions(width.value(), height.value()); + } + + std::optional > Dimensions::parse(nlohmann::json& json, + std::string_view fieldStr, + std::vector& errors) + { + auto& dimJson = json[fieldStr]; + + std::optional width = std::nullopt; + std::optional height = std::nullopt; + + OpenTrackIOHelpers::assignField(dimJson, "width", width, errors); + OpenTrackIOHelpers::assignField(dimJson, "height", height, errors); + + if (!width.has_value() || !height.has_value()) + { + errors.emplace_back(std::format("Key: {} dimensions is missing required fields", fieldStr)); + return std::nullopt; + } + + return Dimensions(width.value(), height.value()); + } + + std::optional Transform::parse(nlohmann::json& json, std::vector& errors) + { + Transform tf{}; + + // Required Fields -------- + std::optional translation = std::nullopt; + std::optional rotation = std::nullopt; + + if (!json.contains("translation") || !json.contains("rotation")) + { + return std::nullopt; + } + + translation = Vector3::parse(json, "translation", errors); + json.erase("translation"); + + rotation = Rotation::parse(json, "rotation", errors); + json.erase("rotation"); + + if (!translation.has_value() || !rotation.has_value()) + { + return std::nullopt; + } + + tf.translation = translation.value(); + tf.rotation = rotation.value(); + + // Non-required fields ------ + if (json.contains("scale")) + { + tf.scale = Vector3::parse(json, "scale", errors); + json.erase("scale"); + } + + OpenTrackIOHelpers::assignField(json, "id", tf.id, errors); + + return tf; + } +} From e757fb20f3e7bda2024137f8e11b82c27250faa6 Mon Sep 17 00:00:00 2001 From: "DISGUISE-LONDON\\nathan.butt" Date: Tue, 7 Jul 2026 14:18:06 +0100 Subject: [PATCH 3/3] Added missing template entries. --- src/OpenTrackIOTypes.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OpenTrackIOTypes.cpp b/src/OpenTrackIOTypes.cpp index 11711e8..e7b180e 100644 --- a/src/OpenTrackIOTypes.cpp +++ b/src/OpenTrackIOTypes.cpp @@ -160,6 +160,7 @@ namespace opentrackio::opentrackiotypes return Timestamp(seconds.value(), nanoseconds.value()); } + template<> std::optional > Dimensions::parse(nlohmann::json& json, std::string_view fieldStr, std::vector& errors) @@ -181,6 +182,7 @@ namespace opentrackio::opentrackiotypes return Dimensions(width.value(), height.value()); } + template<> std::optional > Dimensions::parse(nlohmann::json& json, std::string_view fieldStr, std::vector& errors)