is there an example of protobuf with text output?

19,978

Solution 1

The debug string output is guaranteed to be valid text-serialized format, but does not care about whether the protocol message is actually valid:

std::string s = msg.DebugString();  // or ShortDebugString

If you want to validate, use TextFormat::PrintToString:

#include <google/protobuf/text_format.h>

if (std::string s; google::protobuf::TextFormat::PrintToString(msg, &s)) {
  std::cout << "Your message: " << s;
} else {
  std::cerr << "Message not valid (partial content: "
            << msg.ShortDebugString() << ")\n";
}

Tools for JSON interop are available in json_util.h.

Solution 2

This code will serialise protobuf messages to JSON and deserialise JSON to protobuf messages.

This is lifted straight out of production code (which I own and hereby grant you licence to use, but please credit me).

This is linked against protobuf 3.

Header:

struct pretty_json_type {
    void operator()(google::protobuf::util::JsonOptions& opts) const {
        opts.add_whitespace = true;
    }
};
static constexpr pretty_json_type pretty_json{};

struct compact_json_type {
    void operator()(google::protobuf::util::JsonOptions& opts) const {
        opts.add_whitespace = false;
    }
};
static constexpr compact_json_type compact_json{};

struct include_defaults_type {
    void operator()(google::protobuf::util::JsonOptions& opts) const {
        opts.always_print_primitive_fields = true;
    }
};
static constexpr include_defaults_type include_defaults{};

template<class...Options>
auto json_options(Options&&...options)
{
    google::protobuf::util::JsonOptions opts;
    using expand = int [];
    void(expand{
        0,
        ((options(opts)),0)...
    });
    return opts;
}

std::string as_json(const google::protobuf::Message& msg,
                    google::protobuf::util::JsonOptions opts = json_options(pretty_json,
                                                                            include_defaults));

std::string as_json(const google::protobuf::Message* msg,
                    google::protobuf::util::JsonOptions opts = json_options(pretty_json,
                                                                            include_defaults));

google::protobuf::Message& from_json(google::protobuf::Message& msg,
                                     const char* first,
                                     std::size_t size);

inline
decltype(auto) from_json(google::protobuf::Message& msg,
                         const std::string& json)
{
    return from_json(msg, json.data(), json.length());
}

Implementation

std::string as_json(const google::protobuf::Message& msg,
                    google::protobuf::util::JsonOptions opts)
{
    namespace pb = google::protobuf;
    namespace pbu = google::protobuf::util;

    auto buffer = msg.SerializeAsString();
    std::string result;
    pb::io::ArrayInputStream zistream(buffer.data(), buffer.size());

    auto resolver = std::unique_ptr<pbu::TypeResolver> {
        pbu::NewTypeResolverForDescriptorPool("",
                                              pb::DescriptorPool::generated_pool())
    };

    auto status = google::protobuf::util::BinaryToJsonString(resolver.get(),
                                                             "/" + msg.GetDescriptor()->full_name(),
                                                             buffer,
                                                             std::addressof(result),
                                                             opts);
    if (!status.ok())
    {
        std::ostringstream ss;
        ss << status;
        throw std::runtime_error(ss.str());
    }
    return result;
}

std::string as_json(const google::protobuf::Message* msg,
                    google::protobuf::util::JsonOptions opts)
{
    return as_json(*msg, opts);
}


google::protobuf::Message& from_json(google::protobuf::Message& msg,
                                     const char* first,
                                     std::size_t size)
{
    namespace pb = google::protobuf;
    namespace pbu = google::protobuf::util;

    auto resolver = std::unique_ptr<pbu::TypeResolver> {
        pbu::NewTypeResolverForDescriptorPool("", pb::DescriptorPool::generated_pool())
    };

    auto zistream = std::make_unique<pb::io::ArrayInputStream>(first,
                                                               size);
    auto binary_buffer = std::string {};
    binary_buffer.reserve(size);
    auto zostream = std::make_unique<pb::io::StringOutputStream>(std::addressof(binary_buffer));

    auto status = pbu::JsonToBinaryStream(resolver.get(),
                                          "/" + msg.GetDescriptor()->full_name(),
                                          zistream.get(), zostream.get());
    zistream.reset();
    zostream.reset();
    if (msg.ParseFromString(binary_buffer))
    {
        return msg;
    }

    throw std::runtime_error("invalid message");
}

Solution 3

To convert a message to JSON in three lines of code, do this -

#include <google/protobuf/util/json_util.h>

static std::string ProtoToJson(const google::protobuf::Message& proto)
{
  std::string json;
  google::protobuf::util::MessageToJsonString(proto, &json);
  return json;
}
Share:
19,978
darth vader
Author by

darth vader

Updated on June 04, 2022

Comments

  • darth vader
    darth vader almost 2 years

    I want to use protobuf and to create the serialization output file in text format for testing and for a replacement of json. I can't figure out how to write it on my own and am looking for examples. Here is the one on binary output :

    #include <iostream>
    #include <fstream>
    #include <string>
    #include "addressbook.pb.h"
    using namespace std;
    
    // This function fills in a Person message based on user input.
    void PromptForAddress(tutorial::Person* person) {
      cout << "Enter person ID number: ";
      int id;
      cin >> id;
      person->set_id(id);
      cin.ignore(256, '\n');
    
      cout << "Enter name: ";
      getline(cin, *person->mutable_name());
    
      cout << "Enter email address (blank for none): ";
      string email;
      getline(cin, email);
      if (!email.empty()) {
        person->set_email(email);
      }
    
      while (true) {
        cout << "Enter a phone number (or leave blank to finish): ";
        string number;
        getline(cin, number);
        if (number.empty()) {
          break;
        }
    
        tutorial::Person::PhoneNumber* phone_number = person->add_phones();
        phone_number->set_number(number);
    
        cout << "Is this a mobile, home, or work phone? ";
        string type;
        getline(cin, type);
        if (type == "mobile") {
          phone_number->set_type(tutorial::Person::MOBILE);
        } else if (type == "home") {
          phone_number->set_type(tutorial::Person::HOME);
        } else if (type == "work") {
          phone_number->set_type(tutorial::Person::WORK);
        } else {
          cout << "Unknown phone type.  Using default." << endl;
        }
      }
    }
    
    // Main function:  Reads the entire address book from a file,
    //   adds one person based on user input, then writes it back out to the same
    //   file.
    int main(int argc, char* argv[]) {
      // Verify that the version of the library that we linked against is
      // compatible with the version of the headers we compiled against.
      GOOGLE_PROTOBUF_VERIFY_VERSION;
    
      if (argc != 2) {
        cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
        return -1;
      }
    
      tutorial::AddressBook address_book;
    
      {
        // Read the existing address book.
        fstream input(argv[1], ios::in | ios::binary);
        if (!input) {
          cout << argv[1] << ": File not found.  Creating a new file." << endl;
        } else if (!address_book.ParseFromIstream(&input)) {
          cerr << "Failed to parse address book." << endl;
          return -1;
        }
      }
    
      // Add an address.
      PromptForAddress(address_book.add_people());
    
      {
        // Write the new address book back to disk.
        fstream output(argv[1], ios::out | ios::trunc | ios::binary);
        if (!address_book.SerializeToOstream(&output)) {
          cerr << "Failed to write address book." << endl;
          return -1;
        }
      }
    
      // Optional:  Delete all global objects allocated by libprotobuf.
      google::protobuf::ShutdownProtobufLibrary();
    
      return 0;
    }
    

    Can I just do some minor changes in this one to output in text format or something else needs to be done? Please either suggest the changes required or any link where code exists (in any language).