14.3. Serialization Utilities

Fast DDS provides methods to serialize XTypes objects, enabling efficient data exchange and manipulation within distributed systems. Serialization is a crucial process in data distribution services, as it converts complex data structures into a format that can be easily transmitted and reconstructed across different platforms and programming environments.

14.3.1. Dynamic Type to IDL

The method idl_serialize serializes a DynamicType object to its IDL representation.

Note

The conversion to IDL only supports the following builtin annotations: @bit_bound, @extensibility, @key, and @position.

Warning

The conversion to IDL of a Bitset with inheritance merges derived Bitsets with their base Bitset.

Warning

The conversion to IDL dismisses values explicitly set to their default value. For example, the default @bit_bound value of a Bitmask is 32. If a user were to explicitly set the @bit_bound value of a Bitmask to 32 and then serialize the DynamicType to IDL, the @bit_bound would not be included in the IDL.

14.3.1.1. Example: Convert a discovered type to IDL format

The following example demonstrates how to use the idl_serialize method in Fast DDS to convert discovered types to IDL format. Each time the subscriber discovers a new DataReader or DataWriter, it uses the DynamicTypeBuilderFactory to build a DynamicType and serialize it to IDL format. Please refer to Remote type discovery and endpoint matching section for more details on how to implement remote type discovery.

    /* Custom Callback on_data_reader_discovery */
    void on_data_reader_discovery(
            DomainParticipant* /* participant */,
            eprosima::fastdds::rtps::ReaderDiscoveryStatus /* reason */,
            const eprosima::fastdds::dds::SubscriptionBuiltinTopicData& info,
            bool& /* should_be_ignored */) override
    {
        // Get remote type information
        xtypes::TypeObject remote_type_object;
        if (RETCODE_OK != DomainParticipantFactory::get_instance()->type_object_registry().get_type_object(
                    info.type_information.type_information.complete().typeid_with_size().type_id(),
                    remote_type_object))
        {
            // Error
            return;
        }

        // Build remotely discovered type
        DynamicType::_ref_type remote_type = DynamicTypeBuilderFactory::get_instance()->create_type_w_type_object(
            remote_type_object)->build();

        // Serialize DynamicType into its IDL representation
        std::stringstream idl;
        idl_serialize(remote_type, idl);

        // Print IDL representation
        std::cout << "Type discovered:\n" << idl.str() << std::endl;
    }

    /* Custom Callback on_data_writer_discovery */
    void on_data_writer_discovery(
            DomainParticipant* /* participant */,
            eprosima::fastdds::rtps::WriterDiscoveryStatus /*reason*/,
            const eprosima::fastdds::dds::PublicationBuiltinTopicData& info,
            bool& /* should_be_ignored */) override
    {
        // Get remote type information
        xtypes::TypeObject remote_type_object;
        if (RETCODE_OK != DomainParticipantFactory::get_instance()->type_object_registry().get_type_object(
                    info.type_information.type_information.complete().typeid_with_size().type_id(),
                    remote_type_object))
        {
            // Error
            return;
        }

        // Build remotely discovered type
        DynamicType::_ref_type remote_type = DynamicTypeBuilderFactory::get_instance()->create_type_w_type_object(
            remote_type_object)->build();

        // Serialize DynamicType into its IDL representation
        std::stringstream idl;
        idl_serialize(remote_type, idl);

        // Print IDL representation
        std::cout << "Type discovered:\n" << idl.str() << std::endl;
    }

14.3.2. DynamicData to JSON

In the context of DDS (Data Distribution Service), DynamicType represents the structure of the data being distributed across the system. Each DynamicData object corresponds to an object of the type represented by its DynamicType, providing functionalities to access and modify data values. To enhance interoperability and readability, it is often useful to serialize DynamicData into a more manageable format, to enable easier data processing and analysis across different systems and applications. The method json_serialize converts a DynamicData object into a JSON object, then dumped into a std::ostream.

14.3.2.1. Supported Types

This section provides the serialization of DynamicData to JSON ostream for all supported types.

14.3.2.1.1. Primitives

Primitive types are the basic building blocks for every DynamicType. Below is an example of the definition of primitive types in IDL:

struct PrimitivesStruct
{
    boolean my_bool;
    octet my_octet;
    char my_char;
    wchar my_wchar;
    long my_long;
    unsigned long my_ulong;
    int8 my_int8;
    uint8 my_uint8;
    short my_short;
    unsigned short my_ushort;
    long long my_longlong;
    unsigned long long my_ulonglong;
    float my_float;
    double my_double;
    long double my_longdouble;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "my_bool": false,
    "my_octet": 0,
    "my_char": "\u0000",
    "my_wchar": "\u0000",
    "my_long": 0,
    "my_ulong": 0,
    "my_int8": 0,
    "my_uint8": 0,
    "my_short": 0,
    "my_ushort": 0,
    "my_longlong": 0,
    "my_ulonglong": 0,
    "my_float": 0.0,
    "my_double": 0.0,
    "my_longdouble": 0.0
}

14.3.2.1.2. Strings

String types are used to represent sequences of characters, which are essential for handling textual data within the system. The following example shows the definition of string types in IDL:

struct StringsStruct
{
    string my_string;
    wstring my_wstring;
    string<41925> my_bounded_string;
    wstring<20925> my_bounded_wstring;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "my_string": "",
    "my_wstring": "",
    "my_bounded_string": "",
    "my_bounded_wstring": ""
}

14.3.2.1.3. Enumerations

Enumeration types represent a fixed set of named values, making it easier to work with a predefined list of options. Below is an example of the definition of enumeration types in IDL:

enum MyEnum
{
    A,
    B,
    C
};

struct EnumStruct
{
    MyEnum my_enum;
};

The previous DynamicData object can be serialized in two different formats, eProsima and OMG, providing flexibility depending on the required interoperability and compatibility with other systems. The previous DynamicData object would be serialized as follows in the different formats:

{
    "my_enum": {
        "name": "A",
        "value": 0
    }
}

14.3.2.1.4. Bitmasks

Bitmask types allow the representation of a set of flags in a single value, which can be useful for storing multiple boolean options compactly. Here is an example of the definition of bitmask types in IDL:

@bit_bound(8)
bitmask MyBitMask
{
    @position(0) flag0,
    flag1,
    flag2,
    @position(5) flag5
};

struct BitmaskStruct
{
    MyBitMask my_bitmask;
};

Bitmask also present different serialized structures in the different formats eProsima and OMG. The previous DynamicData object would be serialized as follows in the different formats:

{
    "my_bitmask": {
        "active": [],
        "binary": "00000000",
        "value": 0
    }
}

14.3.2.1.5. Sequences

Sequence types are used to represent ordered collections of elements, similar to arrays but with dynamic length. Below is an example of the definition of sequence types in IDL:

struct SequenceStruct
{
    sequence<MyBitMask> bitmask_sequence;
    sequence<short, 5> short_sequence;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "bitmask_sequence": [],
    "short_sequence": []
}

14.3.2.1.6. Arrays

Array types are used to represent fixed-size collections of elements. The following example shows the definition of array types in IDL:

struct ArrayStruct
{
    long long_array[2][3];
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "long_array": [
        [
            0,
            0,
            0
        ],
        [
            0,
            0,
            0
        ]
    ]
}

14.3.2.1.7. Maps

Map types represent collections of key-value pairs, allowing for the efficient lookup of values based on unique keys. Below is an example of the definition of map types in IDL:

struct MapStruct
{
    map<string, MyAliasedBoundedString> string_alias_unbounded_map;
    map<short, long, 2> short_long_map;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "short_long_map": null,
    "string_alias_unbounded_map": null
}

14.3.2.1.8. Structures

Structure types are used to group different types of data together. Here is an example of the definition of structure types in IDL:

struct InnerStruct
{
    @id(0x10) long first;
};

struct ParentStruct
{
    float first;
    long long second;
};

struct ComplexStruct : ParentStruct
{
    InnerStruct complex_member;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "complex_member": {
        "first": 0
    },
    "first": 0.0,
    "second": 0
}

14.3.2.1.9. Unions

Union types are a special type of structure type where only one member exists. Below is an example of the definition of union types in IDL:

union InnerUnion switch (short)
{
    case 0:
        @id(0x10) PrimitivesStruct first;
    case 1:
    default:
        long long second;
};

union ComplexUnion switch (long)
{
    case 0:
    case 1:
        long third;
    default:
        InnerUnion fourth;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "inner_union": {
        "second": 0
    },
    "complex_union": {
        "fourth": {
            "second": 0
        }
    }
}

14.3.2.1.10. Bitsets

Bitset types are a specialized type of structure where individual bits can be accessed and manipulated. Below is an example of the definition of bitset types in IDL:

bitset MyBitSet
{
    bitfield<3> a;
    bitfield<1> b;
    bitfield<4>;
    bitfield<10> c;
    bitfield<12, short> d;
};

struct BitsetStruct
{
   MyBitSet my_bitset;
};

A DynamicData object corresponding to the type represented above would be serialized as follows:

{
    "my_bitset": {
        "a": 0,
        "b": 0,
        "c": 0,
        "d": 0
    }
}

14.3.2.2. Example: Convert received data into JSON format

The following code demonstrates how to use the json_serialize function in Fast DDS to serialize received data into a more manageable and understandable JSON format. Each time the subscriber receives new data, the corresponding DynamicData can be obtained from the DynamicDataFactory and serialized into a JSON string format. Please refer to Remote type discovery and endpoint matching section for more details on how to implement remote type discovery.

void on_data_available(
        DataReader* reader)
{
    // Dynamic DataType
    DynamicData::_ref_type new_data =
            DynamicDataFactory::get_instance()->create_data(dyn_type_);

    SampleInfo info;

    while ((RETCODE_OK == reader->take_next_sample(&new_data, &info)))
    {
        std::stringstream output;
        output << std::setw(4);

        // Serialize DynamicData into JSON string format
        json_serialize(new_data, DynamicDataJsonFormat::EPROSIMA, output);
        std::cout << "Message received:\n" << output.str() << std::endl;
    }
}

// DynamicType created in discovery callback
DynamicType::_ref_type dyn_type_;