Reputation: 1308
I'd like to create a JSON string containing the instance variables of my class.
For example,
class Example {
std::string string;
std::map<std::string, std:string> map;
std::vector<int> vector;
};
would become:
{
"string":"the-string-value",
"map": {
"key1":"val1",
"key2":"val2"
},
"vector":[1,2,3,4]
}
I've looked into several C++ libraries for creating JSON and they all seem incredibly complex. I'd like something similar to Javascript's JSON.stringify(object)
. In other words just pass a std::map to it and receive a string. The map could contain other maps, vectors, lists, strings, numbers and bools.
What's the nicest way to do this?
Thanks for your help.
Edit
I've looked into the following:
json spirit, jsoncpp, zoolib, JOST, CAJUN, libjson, nosjob, JsonBox, jsonme--
Which I understand I can construct a separate JSON object as in an answer below and convert to JSON I'd like to be able to store my stuff in standard collections and convert.
Edit 2
Okay, scrap the idea of serializing a class since it appears that's impossible with C++'s lack of reflection.
Is there a nice way to convert a std::map containing std:maps, std::vectors, std::lists, numbers, strings, and bools to JSON without having to change datatypes or copying data to a new datatype?
Thanks.
Upvotes: 70
Views: 113084
Reputation: 151
This is a very light-weight solution that allows you to selectively store member variables of your existing classes / structs without the need of manually creating and maintaining your keys and values in a custom dictionary. It only uses nlohmann json (no boost or other heavy library).
nlohmann::json allows you to have arbitrary type conversions. It's described here https://nlohmann.github.io/json/features/arbitrary_types/ in more detail.
Code:
// example.cc
#include <string>
#include <iostream>
#include <map>
#include <vector>
#include "third_party/nlohmann/json.hpp"
struct Example {
std::string name;
std::map<std::string, int> dict;
std::vector<int> vec;
std::string this_member_is_not_part_of_the_json;
};
// Use NLOHMANN_DEFINE_TYPE_INTRUSIVE if your members are private.
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Example,
name,
dict,
vec);
int main() {
Example example;
example.name = "Example";
example.dict = {{"AnyKey", 42}};
example.vec = {1, 2, 3};
nlohmann::json json;
to_json(json, example); // "generated" function
std::cout << json.dump(1) << std::endl;
return 0;
}
Compile the code:
g++ -I third_party/nlohmann/ example.cc -o example
Running ./example
outputs:
{
"dict": {
"AnyKey": 42
},
"name": "Example",
"vec": [
1,
2,
3
]
}
Explanation:
Note, how I used NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE
outside of the struct definition this generates the function to_json(...)
in the same namespace.
json.dump(1)
converts the json to a formatted string.Upvotes: 2
Reputation: 1688
I am the author of https://github.com/beached/daw_json_link . You are correct that C++ does not have reflection at this time and it would be nice for getting the simple stuff out of the way. But by defining a simple declarative mapping, JSON Link will provide a type checked parser for your type(s). For example, the class you specified, could be mapped like:
class Example {
std::string string;
std::map<std::string, std:string> map;
std::vector<int> vector;
};
namespace daw::json {
template<>
struct json_data_contract<Example> {
using type = json_member_list<
json_link<"string", std::string>,
json_link<"map", std::map<std::string, std::string>>
json_link<"vector", std::vector<int>>>;
static inline auto to_json_data( Example const & v ) {
return std::forward_as_tuple( v.string, v.map, v.vector );
}
};
}
From here you can use this mapping inside another as a json_class<"Name", Example>
. To serialize as you ask it is just a matter of auto json_document = daw::json::to_json( MyExampleValue )
or to parse it daw::json::from_json<Example>( json_document );
. The beauty of the library is that it generates a custom parser for your type and does type checking of the data as it is parsing.
Upvotes: 2
Reputation: 177
In RareCpp I've created a very effective JSON Library on top of a reflection implementation. It's written for C++17 and works with Visual Studios, g++, and Clang. The library is header only, meaning you need only copy the reflect and json headers into your project to use it.
The JSON library only requires that you list the fields once in the REFLECT macro; from there it auto identifies appropriate JSON output representations for reading or writing, and can recursively traverse any reflected fields, as well as arrays, STL containers, references, pointers, and more.
struct MyOtherObject { int myOtherInt; REFLECT(MyOtherObject, myOtherInt) };
struct MyObject
{
int myInt;
std::string myString;
MyOtherObject myOtherObject;
std::vector<int> myIntCollection;
REFLECT(MyObject, myInt, myString, myOtherObject, myIntCollection)
};
int main()
{
MyObject myObject = {};
std::cout << "Enter MyObject:" << std::endl;
std::cin >> Json::in(myObject);
std::cout << std::endl << std::endl << "You entered:" << std::endl;
std::cout << Json::pretty(myObject);
}
The above could be ran like so...
Enter MyObject:
{
"myInt": 1337, "myString": "stringy", "myIntCollection": [2,4,6],
"myOtherObject": {
"myOtherInt": 9001
}
}
You entered:
{
"myInt": 1337,
"myString": "stringy",
"myOtherObject": {
"myOtherInt": 9001
},
"myIntCollection": [ 2, 4, 6 ]
}
You can also annotate fields to do things like give them a different name in the JSON representation, force them to be strings, and so on.
struct Point
{
NOTE(latitude, Json::Name{"lat"})
double latitude;
NOTE(longitude, Json::Name{"long"})
double longitude;
REFLECT(Point, latitude, longitude)
};
See here for more examples, there are many other features such as capturing super classes, support for reading, traversing, and writing JSON not known at compile time, further customizing JSON representations for specific fields or types, and more.
Upvotes: 0
Reputation: 98
You could use Boost.PropertyTree.
#include <map>
#include <vector>
#include <string>
#include <iostream>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
namespace pt = boost::property_tree;
int main() {
// Create an empty property tree object.
pt::ptree tree;
// Put a string value into the tree.
tree.put("string", "the-string-value");
// Put a map object into the tree.
pt::ptree child1;
std::map<std::string, std::string> map = {{"key1", "val1"},
{"key2", "val2"}};
for (auto &p : map) {
child1.add(p.first, p.second);
}
tree.add_child("map", child1);
// Put a vector of numbers into the tree
pt::ptree child2;
std::vector<int> vector = {1, 2, 3, 4};
for (auto &v : vector) {
pt::ptree item;
item.put("", v);
child2.push_back(std::make_pair("", item));
}
tree.add_child("vector", child2);
// Write property tree to JSON file
pt::write_json("output.json", tree);
return 0;
}
Output:
{
"string": "the-string-value",
"map": {
"key1": "val1",
"key2": "val2"
},
"vector": [
"1",
"2",
"3",
"4"
]
}
Upvotes: 1
Reputation: 392979
JSON Spirit would allow you to do it like so:
Object addr_obj;
addr_obj.push_back( Pair( "house_number", 42 ) );
addr_obj.push_back( Pair( "road", "East Street" ) );
addr_obj.push_back( Pair( "town", "Newtown" ) );
ofstream os( "address.txt" );
os.write( addr_obj, os, pretty_print );
os.close();
Output:
{
"house_number" : 42,
"road" : "East Street",
"town" : "Newtown"
}
The json_map_demo.cpp would be a nice place to start, I suppose.
Upvotes: 30
Reputation: 340
If the question is still actual, then look at json_dto library, a small header-only helper for converting data between JSON representation and c++ structs.
For example having the following structs:
struct message_source_t
{
// Worker thread.
std::int32_t m_thread_id;
// Sender.
std::string m_subsystem;
};
struct message_t
{
// Who sent a message.
message_source_t m_from;
// When the message was sent (unixtime).
std::tm m_when;
// Message text.
std::string m_text;
};
with the help of json_dto you can create the following JSON:
{
"from" :
{
"thread_id" : 4242,
"sybsystem" : "json_dto"
},
"when" : "2016.09.28 19:55:00",
"text" : "Hello world!"
}
And given such JSON string you can convert it to structs.
Upvotes: -1
Reputation: 23921
Do you want to JSON-ify a map or an object? (your example shows a class, yet you say a map). For a map, check out this library - JSON Spirit.
For objects: There is no reflection support in C++ (apart from the very limited RTTI), so there is no "one-click" solution for serialization either. Any solution will require you to write additional, possibly tightly coupled code to the class you want to serialize and de-serialize (that depends on if you want to serialize non-public data).
Upvotes: 4
Reputation: 4950
Any good C++ JSON library should do this and it is sad to see that they don't -- with the exception of ThorsSerializer and apparently Nosjob as mentioned in this question.
Of course, C++ does not have reflection like Java, so you have to explicitly annotate your types:
(copied from the ThorsSerializer documentation)
#include "ThorSerialize/JsonThor.h"
#include "ThorSerialize/SerUtil.h"
#include <map>
#include <vector>
#include <string>
#include <iostream>
class Example {
std::string string;
std::map<std::string, std::string> map;
std::vector<int> vector;
// Allow access to the class by the serialization library.
friend class ThorsAnvil::Serialize::Traits<Example>;
public:
Example(std::string const& s, std::map<std::string, std::string> const& m, std::vector<int> const& v)
: string(s), map(m), vector(v)
{}
};
// Define what members need to be serilizable
ThorsAnvil_MakeTrait(Example, string, map, vector);
Example Usage:
int main()
{
using ThorsAnvil::Serialize::jsonExport;
using ThorsAnvil::Serialize::jsonImport;
Example e1 {"Some Text", {{"ace", "the best"}, {"king", "second best"}}, {1 ,2 ,3, 4}};
// Simply serialize object to json using a stream.
std::cout << jsonExport(e1) << "\n";
// Deserialize json text from a stream into object.
std::cin >> jsonImport(e1);
}
Running:
{
"string": "Some Text",
"map":
{
"ace": "the best",
"king": "second best"
},
"vector": [ 1, 2, 3, 4]
}
You cannot do better than this in C++.
Upvotes: 18
Reputation: 2389
Have you looked at cereal (http://uscilab.github.io/cereal/) ? It has JSON archives for serializing to/from JSON using C++.
An example with minimal overhead (from cereal) can be found here on SO: https://stackoverflow.com/a/22587527/255635
Upvotes: 2
Reputation: 418
this python script generates c++ pod classes with one member for each json property
you want quite the opposite thing, but is trivial to generate a mapping class which does both loading and saving
generated code relies on an external json parser library
Upvotes: -1
Reputation: 324
I have written an experimental library that can do the job, but it requires external description of classes structures and hierarchy. It uses GCCXML to build an xml dictionary, used for serialization de-serialization :
http://code.google.com/p/cjson/
It's for the moment an experimental project, that can deals with fundamental types (int, float double), pointers to fundamentals types, classes, inherited members etc ... It implements basic std::vector ans std::map serialization, and also std::string instances.
See details for implementation here
Upvotes: 1
Reputation: 688
I wrote a library which designed to solve your problem. However, it is a very new project, not stable enough. Feel free to take a look, the homepage is here::
https://github.com/Mizuchi/acml
In your example, you have to add one line like this:
ACML_REGISTER(Example, ,(string)(map)(vector));
in order to tell the library which member you want to dump. Since C++ have no reflection. And you must give a way to access the member, either use public member level or use friend class.
And later you just need to do sth like this:
string result = acml::json::dumps(any_object);
would become::
{
"string": "the-string-value",
"map":
{
"key1": "val1",
"key2": "val2"
},
"vector":
{
"type": "std::vector",
"size": "4",
"0": "1",
"1": "2",
"2": "3",
"3": "4"
}
}
As you see, JSON array is not implemented yet. And everything becomes string now.
Upvotes: 4