Serializing and Deserializing JSON with Boost

Serializing and deserializing JSON with Boost

Note that property_tree interprets the keys as paths, e.g. putting the pair "a.b"="z" will create an {"a":{"b":"z"}} JSON, not an {"a.b":"z"}. Otherwise, using property_tree is trivial. Here is a little example.

#include <sstream>
#include <map>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

using boost::property_tree::ptree;
using boost::property_tree::read_json;
using boost::property_tree::write_json;

void example() {
// Write json.
ptree pt;
pt.put ("foo", "bar");
std::ostringstream buf;
write_json (buf, pt, false);
std::string json = buf.str(); // {"foo":"bar"}

// Read json.
ptree pt2;
std::istringstream is (json);
read_json (is, pt2);
std::string foo = pt2.get<std::string> ("foo");
}

std::string map2json (const std::map<std::string, std::string>& map) {
ptree pt;
for (auto& entry: map)
pt.put (entry.first, entry.second);
std::ostringstream buf;
write_json (buf, pt, false);
return buf.str();
}

Boost serialize object as a json

No there's not such a thing.

You could write your own (by implementing the Archive concept). But I reckon that's not worth the effort. Just use a JSON library.

Here's a sketch of the minimal output-archive model that works with your sample:

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/nvp.hpp>
#include <iostream>
#include <iomanip>
#include <sstream>

struct MyOArchive {
std::ostream& _os;
MyOArchive(std::ostream& os) : _os(os) {}
using is_saving = boost::true_type;

template <typename T>
MyOArchive& operator<<(boost::serialization::nvp<T> const& wrap) {
save(wrap.name(), wrap.value());
return *this;
}

template <typename T>
MyOArchive& operator<<(T const& value) {
return operator<<(const_cast<T&>(value));
}

template <typename T>
MyOArchive& operator<<(T& value) {
save(value);
return *this;
}

template <typename T> MyOArchive& operator&(T const& v) { return operator<<(v); }

bool first_element = true;
void start_property(char const* name) {
if (!first_element) _os << ", ";
first_element = false;
_os << std::quoted(name) << ":";
}
template <typename T> void save(char const* name, T& b) {
start_property(name);
save(b);
}
void save(bool b) { _os << std::boolalpha << b; }
void save(int i) { _os << i; }
void save(std::string& s) { _os << std::quoted(s); }

template <typename T>
void save(T& v) {
using boost::serialization::serialize;
_os << "{";
first_element = true;
serialize(*this, v, 0u);
_os << "}\n";
first_element = false;
}
};

class Animal {
public:
Animal() {}
void set_leg(int l) { legs = l; };
void set_name(std::string s) { name = s; };
void set_ismammal(bool b) { is_mammal = b; };
void print();

private:
friend class boost::serialization::access;

template <typename Archive> void serialize(Archive &ar, unsigned)
{
ar & BOOST_SERIALIZATION_NVP(legs)
& BOOST_SERIALIZATION_NVP(is_mammal)
& BOOST_SERIALIZATION_NVP(name);
}

int legs;
bool is_mammal;
std::string name;
};

void Animal::print() {
std::cout << name << " with " << legs << " legs is " << (is_mammal ? "" : "not ") << "a mammal" << std::endl;
}

void save_obj(const Animal &animal, std::stringstream &stream) {
MyOArchive oa{ stream };
oa << animal;
}

int main() {
std::stringstream stream;
{
Animal animal;
animal.set_name("Horse");
animal.set_leg(4);
animal.set_ismammal(true);

save_obj(animal, stream);
}

std::cout << "stream print: " << stream.str() << std::endl;
}

Prints

stream print: {"legs":4, "is_mammal":true, "name":"Horse"}

CAVEAT


I do not recommend this approach. In fact there are numerous missing things in the above - most notably the fact that it is output-only

Boost JSON serialization format (boost 1.76.0)

To the best of my knowledge that is not a feature. There has been a fair bit of discussion on the boost mailing list when the library was being reviewed prior to acceptance, so if you want you can check the archives for the rationale.

My recollection of it is that the library focuses on a narrow featureset facilitating machine-to-machine transport (99% of JSON, e.g. in restful APIs). That implies a focus on

  • making it correct
  • making it fast

The same thing came up a day or so ago: Is there a way to switch boost::json::serializer to beautified output? (where I quote from the documentation intro section)

Is there a way to switch boost::json::serializer to beautified output?

This is not a feature of the library. The library has goals stated in the intro. It doesn't include user-friendly presentation formatting. In fact, even their number formatting can be said to be down-right user hostile.

The promise of the library centers at data interchange between computer systems:

This library focuses on a common and popular use-case: parsing and serializing to and from a container called value which holds JSON types. Any value which you build can be serialized and then deserialized, guaranteeing that the result will be equal to the original value. Whatever JSON output you produce with this library will be readable by most common JSON implementations in any language.

There's an example that shows how to do simple pretty printing if you want to avoid a large part of the work/have a reasonable starting point to not forget too many things: https://www.boost.org/doc/libs/1_76_0/libs/json/doc/html/json/examples.html

Alternatively, you can use any of the existing JSON libraries with a richer feature set. Keep in mind they will make difference trade offs, so you may run into other limitations.

Serializing std::list into json with boost ptree

Paths in your tree are always strings. The compiler will tell you this in the remainder of the message. Arguably the documentation is a more readable source:

Both key_type and data_type are configurable, but will usually be std::string here

The self_type & put(const path_type & path, const Type & value, Translator tr);

So the essence of the fix is

pt.put(std::to_string(entry.id), entry.code);

I got a little bit side-tracked cleaning up the code, so here goes:

Self Contained Sample

// FILE: some header
#include <ostream>

struct SiteCode {
int id;
int code;

SiteCode(int id, int code) : id(id), code(code)
{ }

friend inline std::ostream &operator<<(std::ostream &out, SiteCode const& site) {
return out << "(" << site.id << "," << site.code << ")";
}
};

#include <list> // I have deleted some header for sake of readability

// FILE: sqliteDB header
class sqliteDB {
using Records = std::list<SiteCode>;
Records _records;

public:
void load();
Records const& get() const { return _records; }
void printList() const;
void writeJson(std::ostream& os) const;
};

// FILE: some sqlpp.hpp utility header (inline implementations only)
#include <memory>
#include <sqlite3.h>

namespace sqlpp {
using database = std::shared_ptr<::sqlite3>;

void perror(int rc) {
if (rc != SQLITE_OK) throw std::runtime_error(::sqlite3_errstr(rc));
}

struct statement {
static statement prepare(database db, std::string const& sql) {
::sqlite3_stmt* stmt = nullptr;
perror(::sqlite3_prepare_v2(db.get(), sql.c_str(), -1, &stmt, 0));

return { handle(stmt, ::sqlite3_finalize), db };
}

int step() { return ::sqlite3_step(_stmt.get()); }
int column_int(int c) { return ::sqlite3_column_int(_stmt.get(), c); }
private:
using handle = std::shared_ptr<::sqlite3_stmt>;
database _db; // keeping it around for the lifetime of _stmt
handle _stmt;

statement(handle&& h, database& db) : _db(db), _stmt(std::move(h)) { }
};

database open(char const* path) {
::sqlite3* db = nullptr;
perror(::sqlite3_open(path, &db));

return database(db, ::sqlite3_close);
}

statement prepare(database db, std::string const& sql) {
return statement::prepare(db, sql);
}
}

// FILE: sqliteDB implementation file
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>

void sqliteDB::load() {
using namespace sqlpp;

auto stmt = prepare(open("/tmp/database.db"), "SELECT ID, CODE FROM SiteCode;");

while (stmt.step() == SQLITE_ROW)
_records.emplace_back(stmt.column_int(0), stmt.column_int(1));
}

void sqliteDB::writeJson(std::ostream& os) const {
using namespace boost::property_tree;
ptree pt;

for (auto &entry : _records)
pt.put(std::to_string(entry.id), entry.code);

write_json(os, pt, false);
}

// FILE: main program
template <typename List>
static void printList(List const& list) {
int s = list.size();
std::cout << "The number of Records is: " << s << "\n";

for (auto& r : list) std::cout << r << " ";
}

void dump(sqliteDB const& db) {
printList(db.get());
std::cout << "\n==============[ AS JSON ]===============\n";
db.writeJson(std::cout);
}

int main() {
sqliteDB db;

std::cout << "before loading: \n";
dump(db);

std::cout << "after loading: \n";
db.load();
dump(db);
}

Just compile as g++ -std=c++11 -g -Wall -Wextra -pedantic main.cpp -lsqlite3 and get:

sehe@desktop:/tmp$ sqlite3 database.db <<< "create table SiteCode (id int primary key, code int);"
sehe@desktop:/tmp$ for a in {1..10}; do echo "insert into SiteCode(ID,CODE) VALUES($a, $RANDOM);"; done | sqlite3 database.db
sehe@desktop:/tmp$ ./test

Output

before loading: 
The number of Records is: 0

==============[ AS JSON ]===============
{}
after loading:
The number of Records is: 10
(1,5591) (2,31578) (3,30641) (4,4850) (5,1628) (6,5133) (7,8798) (8,20601) (9,21213) (10,18222)
==============[ AS JSON ]===============
{"1":"5591","2":"31578","3":"30641","4":"4850","5":"1628","6":"5133","7":"8798","8":"20601","9":"21213","10":"18222"}

C++ JSON Serialization

For that you need reflection in C/C++, which doesn't exist. You need to have some meta data describing the structure of your classes (members, inherited base classes). For the moment C/C++ compilers don't automatically provide that information in built binaries.

I had the same idea in mind, and I used GCC XML project to get this information. It outputs XML data describing class structures.
I have built a project and I'm explaining some key points in this page :

Serialization is easy, but we have to deal with complex data structure implementations (std::string, std::map for example) that play with allocated buffers.
Deserialization is more complex and you need to rebuild your object with all its members, plus references to vtables ... a painful implementation.

For example you can serialize like this:

    // Random class initialization
com::class1* aObject = new com::class1();

for (int i=0; i<10; i++){
aObject->setData(i,i);
}

aObject->pdata = new char[7];
for (int i=0; i<7; i++){
aObject->pdata[i] = 7-i;
}
// dictionary initialization
cjson::dictionary aDict("./data/dictionary.xml");

// json transformation
std::string aJson = aDict.toJson<com::class1>(aObject);

// print encoded class
cout << aJson << std::endl ;

To deserialize data it works like this:

    // decode the object
com::class1* aDecodedObject = aDict.fromJson<com::class1>(aJson);

// modify data
aDecodedObject->setData(4,22);

// json transformation
aJson = aDict.toJson<com::class1>(aDecodedObject);

// print encoded class
cout << aJson << std::endl ;

Ouptuts:

>:~/cjson$ ./main
{"_index":54,"_inner": {"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,4,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
{"_index":54,"_inner":{"_ident":"test","pi":3.141593},"_name":"first","com::class0::_type":"type","com::class0::data":[0,1,2,3,22,5,6,7,8,9],"com::classb::_ref":"ref","com::classm1::_type":"typem1","com::classm1::pdata":[7,6,5,4,3,2,1]}
>:~/cjson$

Usually these implementations are compiler dependent (ABI Specification for example), and require external descriptions to work (GCCXML output), thus are not really easy to integrate to projects.



Related Topics



Leave a reply



Submit