October 20th, Q&A session: Get you issues solved and questions answered!

GitHub logo
Edit

Serialization in Ignite.C++

BinaryType Templates

Most user-defined classes going through Ignite C++ API will be passed over the wire to other cluster nodes. These classes include your data records, compute tasks, and other objects.

Passing objects of these classes over wire requires serialization. For Ignite C++ it can be achieved by providing a BinaryType class template for your object type:

class Address
{
  friend struct ignite::binary::BinaryType<Address>;
public:
  Address() { }

  Address(const std::string& street, int32_t zip) :
  street(street), zip(zip) { }

  const std::string& GetStreet() const
  {
    return street;
  }

  int32_t GetZip() const
  {
    return zip;
  }

private:
  std::string street;
  int32_t zip;
};

template<>
struct ignite::binary::BinaryType<Address>
{
  static int32_t GetTypeId()
  {
    return GetBinaryStringHashCode("Address");
  }

  static void GetTypeName(std::string& name)
  {
    name = "Address";
  }

  static int32_t GetFieldId(const char* name)
  {
    return GetBinaryStringHashCode(name);
  }

  static bool IsNull(const Address& obj)
  {
    return obj.GetZip() && !obj.GetStreet().empty();
  }

  static void GetNull(Address& dst)
  {
    dst = Address();
  }

  static void Write(BinaryWriter& writer, const Address& obj)
  {
    writer.WriteString("street", obj.GetStreet());
    writer.WriteInt32("zip", obj.GetZip());
  }

  static void Read(BinaryReader& reader, Address& dst)
  {
    dst.street = reader.ReadString("street");
    dst.zip = reader.ReadInt32("zip");
  }
};

Also, you can use the raw serialization mode without storing names of the object’s fields in the serialized form. This mode is more compact and performs faster, but disables SQL queries that require to keep the names of the fields in the serialized form:

template<>
struct ignite::binary::BinaryType<Address>
{
  static int32_t GetTypeId()
  {
    return GetBinaryStringHashCode("Address");
  }

  static void GetTypeName(std::string& name)
  {
    name = "Address";
  }

  static int32_t GetFieldId(const char* name)
  {
    return GetBinaryStringHashCode(name);
  }

  static bool IsNull(const Address& obj)
  {
    return false;
  }

  static void GetNull(Address& dst)
  {
    dst = Address();
  }

  static void Write(BinaryWriter& writer, const Address& obj)
  {
    BinaryRawWriter rawWriter = writer.RawWriter();

    rawWriter.WriteString(obj.GetStreet());
    rawWriter.WriteInt32(obj.GetZip());
  }

  static void Read(BinaryReader& reader, Address& dst)
  {
    BinaryRawReader rawReader = reader.RawReader();

    dst.street = rawReader.ReadString();
    dst.zip = rawReader.ReadInt32();
  }
};

Serialization Macros

Ignite C++ defines a set of utility macros that could be used to simplify the BinaryType specialization. Here is a list of such macros with description:

  • IGNITE_BINARY_TYPE_START(T) - Start the binary type’s specialization.

  • IGNITE_BINARY_TYPE_END - End the binary type’s specialization.

  • IGNITE_BINARY_GET_TYPE_ID_AS_CONST(id) - Implementation of GetTypeId() which returns predefined constant id.

  • IGNITE_BINARY_GET_TYPE_ID_AS_HASH(T) - Implementation of GetTypeId() which returns hash of passed type name.

  • IGNITE_BINARY_GET_TYPE_NAME_AS_IS(T) - Implementation of GetTypeName() which returns type name as is.

  • IGNITE_BINARY_GET_FIELD_ID_AS_HASH - Default implementation of GetFieldId() function which returns Java-way hash code of the string.

  • IGNITE_BINARY_IS_NULL_FALSE(T) - Implementation of IsNull() function which always returns false.

  • IGNITE_BINARY_IS_NULL_IF_NULLPTR(T) - Implementation of IsNull() function which return true if passed object is null pointer.

  • IGNITE_BINARY_GET_NULL_DEFAULT_CTOR(T) - Implementation of GetNull() function which returns an instance created with default constructor.

  • IGNITE_BINARY_GET_NULL_NULLPTR(T) - Implementation of GetNull() function which returns NULL pointer.

You can describe the Address class declared earlier using these macros:

namespace ignite
{
  namespace binary
  {
    IGNITE_BINARY_TYPE_START(Address)
      IGNITE_BINARY_GET_TYPE_ID_AS_HASH(Address)
      IGNITE_BINARY_GET_TYPE_NAME_AS_IS(Address)
      IGNITE_BINARY_GET_NULL_DEFAULT_CTOR(Address)
      IGNITE_BINARY_GET_FIELD_ID_AS_HASH

      static bool IsNull(const Address& obj)
      {
        return obj.GetZip() == 0 && !obj.GetStreet().empty();
      }

      static void Write(BinaryWriter& writer, const Address& obj)
      {
        writer.WriteString("street", obj.GetStreet());
        writer.WriteInt32("zip", obj.GetZip());
      }

      static void Read(BinaryReader& reader, Address& dst)
      {
        dst.street = reader.ReadString("street");
        dst.zip = reader.ReadInt32("zip");
      }

    IGNITE_BINARY_TYPE_END
  }
}

Reading and Writing Values

There are several ways for writing and reading data. The first way is to use an object’s value directly:

CustomType val;

// some application code here
// ...

writer.WriteObject<CustomType>("field_name", val);
CustomType val = reader.ReadObject<CustomType>("field_name");

The second approach does the same but uses a pointer to the object:

// Writing null to as a value for integer field.
writer.WriteObject<int32_t*>("int_field_name", nullptr);

// Writing a value of the custom type by pointer.
CustomType *val;

// some application code here
// ...

writer.WriteObject<CustomType*>("field_name", val);
// Reading value which can be null.
CustomType* nullableVal = reader.ReadObject<CustomType*>("field_name");
if (nullableVal) {
  // ...
}

// You can use a smart pointer as well.
std::unique_ptr<CustomType> nullablePtr = reader.ReadObject<CustomType*>();
if (nullablePtr) {
  // ...
}

An advantage of the pointer-based technique is that it allows writing or reading null values.