ros2_medkit_serialization
This section contains design documentation for the ros2_medkit_serialization package.
Overview
The ros2_medkit_serialization package provides runtime JSON ↔ ROS 2 message conversion
using the dynmsg library.
It enables the gateway to work with any ROS 2 message type without compile-time dependencies.
Architecture
The following diagram shows the relationships between the main components.
ROS 2 Medkit Serialization Class Architecture
Main Components
JsonSerializer - The main API for JSON ↔ ROS 2 message conversion - Stateless and thread-safe design - Provides
serialize()/deserialize()for CDR format (used with GenericPublisher/GenericSubscription) - Providesto_json()/from_json()for in-memory message conversion - Uses dynmsg YAML bridge internally (JSON ↔ YAML ↔ ROS 2 message) - Generates JSON schemas and default values for any message type - Static utilities for YAML ↔ JSON conversionTypeCache - Thread-safe singleton cache for type introspection data - Caches
TypeInfo_Cpp*pointers from dynmsg - Usesstd::shared_mutexfor concurrent read access - Automatic cache miss loading via dynmsgros_type_info_get()- Key format:"package/msg/Type"or"package/srv/Type"ServiceActionTypes - Utility functions for service/action type manipulation - Derives request/response types from service type (e.g.,
std_srvs/srv/SetBool→std_srvs/srv/SetBool_Request) - Derives goal/result/feedback types from action type - Type validation helpers (is_service_type(),is_action_type())Exception Classes - Typed exceptions for error handling -
SerializationError- Base class for all serialization errors -TypeNotFoundError- Message type not found in ROS 2 type system -JsonConversionError- JSON/YAML conversion failed
Data Flow
Publishing (JSON → CDR)
JSON → json_to_yaml() → YAML::Node → dynmsg (set_from_yaml + serialize) → SerializedMessage → GenericPublisher
Subscribing (CDR → JSON)
GenericSubscription → SerializedMessage → dynmsg (deserialize + to_yaml) → YAML::Node → yaml_to_json() → JSON
Service Calls (JSON → CDR → JSON)
Request: JSON → serialize() → SerializedMessage → GenericClient
Response: GenericClient → SerializedMessage → deserialize() → JSON
Design Decisions
YAML Bridge
The package uses dynmsg’s YAML interface rather than direct binary manipulation because:
Stability: dynmsg’s YAML API is well-tested and handles complex nested types
Type Safety: YAML preserves type information during conversion
Debugging: YAML is human-readable for troubleshooting
Performance: Conversion overhead is negligible compared to network I/O
The JSON ↔ YAML conversion layer adds minimal overhead since both formats have similar data models (objects, arrays, scalars).
Singleton TypeCache
The TypeCache uses the singleton pattern because:
Memory Efficiency: Type introspection data is shared across all serializers
Thread Safety: Single mutex protects all cache operations
Lifetime Management: Cache lives for the process duration, matching dynmsg’s design
Thread Safety
JsonSerializeris stateless and can be used from multiple threadsTypeCacheusesstd::shared_mutexfor concurrent readsAll public methods are safe to call from any thread
Usage in Gateway
The serialization package is used by:
DataAccessManager - Publishing to topics via
GenericPublisherOperationManager - Calling services/actions via
GenericClientNativeTopicSampler - Deserializing messages from
GenericSubscription
Example: Publishing to a Topic
JsonSerializer serializer;
// Create publisher
auto publisher = node->create_generic_publisher(
"/cmd_vel", "geometry_msgs/msg/Twist", qos);
// Serialize JSON to CDR
nlohmann::json twist_json = {
{"linear", {{"x", 1.0}, {"y", 0.0}, {"z", 0.0}}},
{"angular", {{"x", 0.0}, {"y", 0.0}, {"z", 0.5}}}
};
auto serialized = serializer.serialize("geometry_msgs/msg/Twist", twist_json);
// Publish
publisher->publish(serialized);
Example: Deserializing from Subscription
JsonSerializer serializer;
auto callback = [&](std::shared_ptr<rclcpp::SerializedMessage> msg) {
nlohmann::json json = serializer.deserialize(
"sensor_msgs/msg/LaserScan", *msg);
// Process json...
};
auto subscription = node->create_generic_subscription(
"/scan", "sensor_msgs/msg/LaserScan", qos, callback);