Serialization

❗️

This is a legacy Apache Ignite documentation

The new documentation is hosted here: https://ignite.apache.org/docs/latest/

Specializing BinaryType template

Most user-defined classes going through Ignite C++ API will be passed over the wire to other grid nodes. These classes include cache keys and values.

Passing objects of these classes over wire requires serialization. For Ignite C++ it can be achieved by specializing BinaryType class template for your 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");
  }
};

Serialization can be also implemented in raw mode, without field names. This provides the fastest and the most compact serialization, but disables queries:

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();
  }
};

Macros

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

  • IGNITE_BINARY_TYPE_START(T) - Start binary type specialization.
  • IGNITE_BINARY_TYPE_END - End binary type 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.

So we can describe Address class declared above using these macros as follows:

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 approaches 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 as a value.