OOP

Data and Object Serialization

1. Binary Serialization

Binary serialization involves writing data in a binary format instead of a human-readable format, which is more efficient in terms of both space and speed.

#include <fstream>

class Person {
public:
    std::string name;
    int age;

    Person() : name(""), age(0) {}
    Person(const std::string& name, int age) : name(name), age(age) {}

    // Serialize the object to binary format
    void serialize(std::ofstream& outFile) const {
        size_t nameLen = name.size();
        outFile.write(reinterpret_cast<const char*>(&nameLen), sizeof(nameLen));
        outFile.write(name.c_str(), nameLen);
        outFile.write(reinterpret_cast<const char*>(&age), sizeof(age));
    }

    // Deserialize the object from binary format
    void deserialize(std::ifstream& inFile) {
        size_t nameLen;
        inFile.read(reinterpret_cast<char*>(&nameLen), sizeof(nameLen));
        name.resize(nameLen);
        inFile.read(&name[0], nameLen);
        inFile.read(reinterpret_cast<char*>(&age), sizeof(age));
    }
};

int main() {
    Person p1("Jane Doe", 25);

    // Serialize to binary file
    std::ofstream outFile("person.bin", std::ios::binary);
    if (outFile.is_open()) {
        p1.serialize(outFile);
        outFile.close();
    }

    Person p2;

    // Deserialize from binary file
    std::ifstream inFile("person.bin", std::ios::binary);
    if (inFile.is_open()) {
        p2.deserialize(inFile);
        inFile.close();
    }

    std::cout << "Deserialized Person: " << p2.name << " " << p2.age << std::endl;

    return 0;
}

2. Error Handling

Proper error handling ensures the robustness of the serialization process.

#include <iostream>
#include <fstream>

int main() {
    Person p1("Jane Doe", 25);

    // Serialize with error handling
    std::ofstream outFile("person.bin", std::ios::binary);
    if (!outFile) {
        std::cerr << "Error opening file for writing" << std::endl;
        return 1;
    }
    p1.serialize(outFile);
    outFile.close();

    Person p2;

    // Deserialize with error handling
    std::ifstream inFile("person.bin", std::ios::binary);
    if (!inFile) {
        std::cerr << "Error opening file for reading" << std::endl;
        return 1;
    }
    p2.deserialize(inFile);
    if (inFile.fail()) {
        std::cerr << "Error reading from file" << std::endl;
        return 1;
    }
    inFile.close();

    std::cout << "Deserialized Person: " << p2.name << " " << p2.age << std::endl;

    return 0;
}

3. Complex Objects

When dealing with complex objects, ensure all members, including pointers and nested objects, are correctly serialized.

#include <vector>

class Person {
public:
    std::string name;
    int age;
    std::vector<int> scores;

    // Serialization
    void serialize(std::ofstream& outFile) const {
        size_t nameLen = name.size();
        outFile.write(reinterpret_cast<const char*>(&nameLen), sizeof(nameLen));
        outFile.write(name.c_str(), nameLen);
        outFile.write(reinterpret_cast<const char*>(&age), sizeof(age));

        size_t scoresSize = scores.size();
        outFile.write(reinterpret_cast<const char*>(&scoresSize), sizeof(scoresSize));
        outFile.write(reinterpret_cast<const char*>(scores.data()), scoresSize * sizeof(int));
    }

    // Deserialization
    void deserialize(std::ifstream& inFile) {
        size_t nameLen;
        inFile.read(reinterpret_cast<char*>(&nameLen), sizeof(nameLen));
        name.resize(nameLen);
        inFile.read(&name[0], nameLen);
        inFile.read(reinterpret_cast<char*>(&age), sizeof(age));

        size_t scoresSize;
        inFile.read(reinterpret_cast<char*>(&scoresSize), sizeof(scoresSize));
        scores.resize(scoresSize);
        inFile.read(reinterpret_cast<char*>(scores.data()), scoresSize * sizeof(int));
    }
};

4. Custom Data Formats

Using libraries for JSON or XML serialization can simplify handling complex data formats and provide better interoperability.

Using JSON with a Library (e.g., nlohmann/json):

link to code

5. Versioning

Handling versioning requires maintaining backward compatibility when class definitions change over time.

class Person {
public:
    std::string name;
    int age;
    std::string address;  // Added in a new version

    // Serialization with versioning
    void serialize(std::ofstream& outFile) const {
        int version = 2;  // Version number
        outFile.write(reinterpret_cast<const char*>(&version), sizeof(version));
        size_t nameLen = name.size();
        outFile.write(reinterpret_cast<const char*>(&nameLen), sizeof(nameLen));
        outFile.write(name.c_str(), nameLen);
        outFile.write(reinterpret_cast<const char*>(&age), sizeof(age));
        
        // Serialize new field
        size_t addressLen = address.size();
        outFile.write(reinterpret_cast<const char*>(&addressLen), sizeof(addressLen));
        outFile.write(address.c_str(), addressLen);
    }

    // Deserialization with versioning
    void deserialize(std::ifstream& inFile) {
        int version;
        inFile.read(reinterpret_cast<char*>(&version), sizeof(version));
        size_t nameLen;
        inFile.read(reinterpret_cast<char*>(&nameLen), sizeof(nameLen));
        name.resize(nameLen);
        inFile.read(&name[0], nameLen);
        inFile.read(reinterpret_cast<char*>(&age), sizeof(age));

        if (version >= 2) {
            // Deserialize new field
            size_t addressLen;
            inFile.read(reinterpret_cast<char*>(&addressLen), sizeof(addressLen));
            address.resize(addressLen);
            inFile.read(&address[0], addressLen);
        }
    }
};