bad_locality
bad_locality

Reputation: 165

Problems with repeated field in Protobuf. What is a better way to use the repeated field for serialization/deserialization?

Consider the following sensor.proto file that makes use of the repeated field to initialize multiple messages.

syntax = "proto3";

package HUBSensors;

message Device {
    string name = 1;
    int32 id = 2;

    message Sensor {
        string name = 1;
        double temperature = 2;
        int32 humidity = 3;

        enum SwitchLevel {
            CLOSED = 0;
            OPEN = 1;
        }

        SwitchLevel door = 4;
    }

    repeated Sensor sensors = 3;
}

Now I want to serialize some random data to a file. As an example, I will have one device with multiple sensors, hence the repeated file in the proto. I use the following code.

inline void serialize_to_file( const std::string &fileName )
{
    HUBSensors::Device device;

    device.set_name("HUB");
    device.set_id(1234);

    device.add_sensors()->set_name("Laboratory");
    device.add_sensors()->set_temperature(23.3);
    device.add_sensors()->set_humidity(2);
    device.add_sensors()->set_door(HUBSensors::Device_Sensor_SwitchLevel_OPEN);

    device.add_sensors()->set_name("Chml Laboratory");
    device.add_sensors()->set_temperature(2.3);
    device.add_sensors()->set_humidity(5);
    device.add_sensors()->set_door(HUBSensors::Device_Sensor_SwitchLevel_CLOSED);

    device.add_sensors()->set_name("GU Laboratory");
    device.add_sensors()->set_temperature(8.3);
    device.add_sensors()->set_humidity(2);
    device.add_sensors()->set_door(HUBSensors::Device_Sensor_SwitchLevel_CLOSED);


    std::ofstream ofs(fileName, std::ios_base::out | std::ios_base::binary );
    device.SerializeToOstream( &ofs );
    google::protobuf::ShutdownProtobufLibrary();
}

To parse the data and print out the result I use the following:

inline void parse_from_file( const std::string &fileName )
{
    HUBSensors::Device device;
    std::ifstream myFile;
    myFile.exceptions( std::ifstream::badbit );

    try {
        myFile.open(fileName);

        while ( myFile.good() )
        {
            device.ParseFromIstream( &myFile );

            //std::cout << device.sensors_size() << std::endl;

            std::cout << "Device Name : " << device.name() << std::endl;
            std::cout << "^^^^^^" << std::endl;
            for ( size_t i = 0; i < device.sensors_size(); i+=4)
            {
                std::cout << "Sensors Name : " << device.sensors(i).name() << std::endl;
                std::cout << "Temperature : " << device.sensors(i+1).temperature() << std::endl;
                std::cout << "Humidity : " << device.sensors(i+2).humidity() << std::endl;
                std::cout << " Door Status :  " << device.sensors(i+3).door() << std::endl;
                std::cout << "^^^^^^" << std::endl;
            }
        }
    }
    catch ( const std::ifstream::failure &e )
    {
        std::cerr << "Error Occurred when accessing the file" << std::endl;
        std::cout << e.what() << std::endl;
    }
    myFile.close();
    google::protobuf::ShutdownProtobufLibrary();
}

A main file to reproduce the results:

#include <iostream>
#include <fstream>
#include <stdexcept>

#include "sensor.pb.h"


int main() {

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    const std::string fileName = "./device.data";

    serialize_to_file( fileName );
    parse_from_file( fileName );

    return 0;
}

It doesn't seem intuitive to iterate through the total sensor size and getting the correct index to display the appropriate field. Even by checking

std::cout << device.sensors_size() << std::endl;

will output size 12 which it does not feel correct, or

std::cout << decice.sensors(2).name() << std::endl;

will not output anything since it is in the incorrect index. What is a better way to use the libprotobuf to define a repeated field for serializing/deserializing better.


EDIT: As the answer suggests, serializing to the file should be as


inline void serialize_to_file( const std::string &fileName )
{
    HUBSensors::Device device;

    device.set_name("HUB");
    device.set_id(1234);

    auto sensor1 = device.add_sensors();
    auto sensor2 = device.add_sensors();
    auto sensor3 = device.add_sensors();

    sensor1->set_name("Laboratory");
    sensor1->set_temperature(23.3);
    sensor1->set_humidity(2);
    sensor1->set_door(HUBSensors::Device_Sensor_SwitchLevel_CLOSED);

    sensor2->set_name("GU Laboratory");
    sensor2->set_temperature(44.3);
    sensor2->set_humidity(4);
    sensor2->set_door(HUBSensors::Device_Sensor_SwitchLevel_OPEN);

    sensor3->set_name("Chml Laboratory");
    sensor3->set_temperature(13.345);
    sensor3->set_humidity(6);
    sensor3->set_door(HUBSensors::Device_Sensor_SwitchLevel_CLOSED);

    std::ofstream ofs(fileName, std::ios_base::out | std::ios_base::binary );
    device.SerializeToOstream( &ofs );
    google::protobuf::ShutdownProtobufLibrary();
}

Upvotes: 0

Views: 2698

Answers (1)

Bal&#225;zs Kovacsics
Bal&#225;zs Kovacsics

Reputation: 701

Instead of

    device.add_sensors()->set_name("Laboratory");
    device.add_sensors()->set_temperature(23.3);
    device.add_sensors()->set_humidity(2);
    device.add_sensors()->set_door(HUBSensors::Device_Sensor_SwitchLevel_OPEN);

you should write

    auto sensor = device.add_sensors();
    sensor->set_name("Laboratory");
    sensor->set_temperature(23.3);
    sensor->set_humidity(2);
    sensor->set_door(HUBSensors::Device_Sensor_SwitchLevel_OPEN);

This way you will have 3 sensors, which is your intent I suppose, and all of them will have every data member set.

Upvotes: 3

Related Questions