Parse JSON from a string
Parse JSON from a file
Parse JSON from an iterator range
Parse numbers without loosing precision
Validate JSON without incurring parse exceptions
How to allow comments? How not to?
Set a maximum nesting depth
Prevent the alphabetic sort of the outputted JSON, retaining the original insertion order
Decode a JSON text using stateful result and work allocators
Encode a json value to a string
Encode a json value to a stream
Escape all non-ascii characters
Replace the representation of NaN, Inf and -Inf when serializing. And when reading in again.
Write some JSON (push)
Read some JSON (pull)
Filter the event stream
Pull nested objects into a basic_json
Iterate over basic_json items
Iterate over strongly typed items
Serialize with the C++ member names of the class
Serialize with provided names using the _NAME_
macros
Mapping to C++ data structures with and without defaults allowed
Specialize json_type_traits explicitly
Serialize non-mandatory std::optional values using the convenience macros
An example with std::shared_ptr and std::unique_ptr
Serialize a templated class with the _TPL_
macros
An example using JSONCONS_ENUM_TRAITS and JSONCONS_ALL_CTOR_GETTER_TRAITS
Serialize a polymorphic type based on the presence of members
Ensuring type selection is possible
An example with std::variant
Type selection and std::variant
Convert JSON numbers to/from boost multiprecision numbers
Construct a json object
Construct a json array
Insert a new value in an array at a specific position
Merge two json objects
Construct a json byte string
Construct a multidimensional json array
Construct a json array that contains non-owning references to other json values (since 0.156.0)
Use string_view to access the actual memory that's being used to hold a string
Given a string in a json object that represents a decimal number, assign it to a double
Retrieve a big integer that's been parsed as a string
Look up a key, if found, return the value converted to type T, otherwise, return a default value of type T
Retrieve a value in a hierarchy of JSON objects
Retrieve a json value as a byte string
Iterate over a json array
Iterate over a json object
Flatten a json object with numberish keys to JSON Pointer/value pairs
Flatten a json object to JSONPath/value pairs
Search for and repace an object member key
Search for and replace a value
Update JSON in place
std::string s = R"({"first":1,"second":2,"fourth":3,"fifth":4})";
json j = json::parse(s);
or
using namespace jsoncons::literals;
json j = R"(
{
"StartDate" : "2017-03-01",
"MaturityDate" : "2020-12-30"
}
)"_json;
See basic_json::parse.
std::ifstream is("myfile.json");
json j = json::parse(is);
See basic_json::parse.
#include <jsoncons/json.hpp>
class MyIterator
{
const char* p_;
public:
using iterator_category = std::input_iterator_tag;
using value_type = char;
using difference_type = std::ptrdiff_t;
using pointer = const char*;
using reference = const char&;
MyIterator(const char* p)
: p_(p)
{
}
reference operator*() const
{
return *p_;
}
pointer operator->() const
{
return p_;
}
MyIterator& operator++()
{
++p_;
return *this;
}
MyIterator operator++(int)
{
MyIterator temp(*this);
++*this;
return temp;
}
bool operator!=(const MyIterator& rhs) const
{
return p_ != rhs.p_;
}
};
int main()
{
char source[] = {'[','\"', 'f','o','o','\"',',','\"', 'b','a','r','\"',']'};
MyIterator first(source);
MyIterator last(source + sizeof(source));
json j = json::parse(first, last);
std::cout << j << "\n\n";
}
Output:
["foo","bar"]
See basic_json::parse.
By default, jsoncons parses a number with an exponent or fractional part
into a double precision floating point number. If you wish, you can
keep the number as a string with semantic tagging bigdec
,
using the lossless_number
option. You can then put it into a float
,
double
, a boost multiprecision number, or whatever other type you want.
#include <jsoncons/json.hpp>
int main()
{
std::string s = R"(
{
"a" : 12.00,
"b" : 1.23456789012345678901234567890
}
)";
// Default
json j = json::parse(s);
std::cout.precision(15);
// Access as string
std::cout << "(1) a: " << j["a"].as<std::string>() << ", b: " << j["b"].as<std::string>() << "\n";
// Access as double
std::cout << "(2) a: " << j["a"].as<double>() << ", b: " << j["b"].as<double>() << "\n\n";
// Using lossless_number option
json_options options;
options.lossless_number(true);
json j2 = json::parse(s, options);
// Access as string
std::cout << "(3) a: " << j2["a"].as<std::string>() << ", b: " << j2["b"].as<std::string>() << "\n";
// Access as double
std::cout << "(4) a: " << j2["a"].as<double>() << ", b: " << j2["b"].as<double>() << "\n\n";
}
Output:
(1) a: 12.0, b: 1.2345678901234567
(2) a: 12, b: 1.23456789012346
(3) a: 12.00, b: 1.23456789012345678901234567890
(4) a: 12, b: 1.23456789012346
std::string s = R"(
{
"StartDate" : "2017-03-01",
"MaturityDate" "2020-12-30"
}
)";
json_reader reader(s);
// or,
// std::stringstream is(s);
// json_reader reader(is);
std::error_code ec;
reader.read(ec);
if (ec)
{
std::cout << ec.message()
<< " on line " << reader.line()
<< " and column " << reader.column()
<< std::endl;
}
Output:
Expected name separator ':' on line 4 and column 20
jsoncons, by default, accepts and ignores C-style comments
std::string s = R"(
{
// Single line comments
/*
Multi line comments
*/
}
)";
// Default
json j = json::parse(s);
std::cout << "(1) " << j << std::endl;
// Strict
try
{
json j = json::parse(s, strict_json_parsing());
}
catch (const ser_error& e)
{
std::cout << "(2) " << e.what() << std::endl;
}
Output:
(1) {}
(2) Illegal comment at line 3 and column 10
Like this,
std::string s = "[[[[[[[[[[[[[[[[[[[[[\"Too deep\"]]]]]]]]]]]]]]]]]]]]]";
try
{
json_options options;
options.max_nesting_depth(20);
json j = json::parse(s, options);
}
catch (const ser_error& e)
{
std::cout << e.what() << std::endl;
}
Output:
Maximum JSON depth exceeded at line 1 and column 21
Use ojson
instead of json
(or wojson
instead of wjson
) to retain the original insertion order.
ojson j = ojson::parse(R"(
{
"street_number" : "100",
"street_name" : "Queen St W",
"city" : "Toronto",
"country" : "Canada"
}
)");
std::cout << "(1)\n" << pretty_print(j) << std::endl;
// Insert "postal_code" at end
j.insert_or_assign("postal_code", "M5H 2N2");
std::cout << "(2)\n" << pretty_print(j) << std::endl;
// Insert "province" before "country"
auto it = j.find("country");
j.insert_or_assign(it,"province","Ontario");
std::cout << "(3)\n" << pretty_print(j) << std::endl;
Output:
(1)
{
"street_number": "100",
"street_name": "Queen St W",
"city": "Toronto",
"country": "Canada"
}
(2)
{
"street_number": "100",
"street_name": "Queen St W",
"city": "Toronto",
"country": "Canada",
"postal_code": "M5H 2N2"
}
(3)
{
"street_number": "100",
"street_name": "Queen St W",
"city": "Toronto",
"province": "Ontario",
"country": "Canada",
"postal_code": "M5H 2N2"
}
// Given allocator my_alloc with a single-argument constructor my_alloc(int),
// use my_alloc(1) to allocate basic_json memory, my_alloc(2) to allocate
// working memory used by json_decoder, and my_alloc(3) to allocate
// working memory used by basic_json_reader.
using my_json = basic_json<char,sorted_policy,my_alloc>;
std::ifstream is("book_catalog.json");
json_decoder<my_json,my_alloc> decoder(my_alloc(1),my_alloc(2));
basic_json_reader<char,stream_source<char>,my_alloc> reader(is, decoder, my_alloc(3));
reader.read();
my_json j = decoder.get_result();
std::cout << pretty_print(j) << "\n";
std::string s;
j.dump(s); // compressed
j.dump_pretty(s); // pretty print
j.dump(std::cout); // compressed
j.dump_pretty(std::cout); // pretty print
or
std::cout << j << std::endl; // compressed
std::cout << pretty_print(j) << std::endl; // pretty print
json_options options;
options.escape_all_non_ascii(true);
j.dump(std::cout, options); // compressed
j.dump_pretty(std::cout, options); // pretty print
or
std::cout << print(j, options) << std::endl; // compressed
std::cout << pretty_print(j, options) << std::endl; // pretty print
Set the serializing options for nan
and inf
to distinct string values.
json j;
j["field1"] = std::sqrt(-1.0);
j["field2"] = 1.79e308 * 1000;
j["field3"] = -1.79e308 * 1000;
json_options options;
options.nan_to_str("NaN")
.inf_to_str("Inf");
std::ostringstream os;
os << pretty_print(j, options);
std::cout << "(1)\n" << os.str() << std::endl;
json j2 = json::parse(os.str(),options);
std::cout << "\n(2) " << j2["field1"].as<double>() << std::endl;
std::cout << "(3) " << j2["field2"].as<double>() << std::endl;
std::cout << "(4) " << j2["field3"].as<double>() << std::endl;
std::cout << "\n(5)\n" << pretty_print(j2,options) << std::endl;
Output:
(1)
{
"field1": "NaN",
"field2": "Inf",
"field3": "-Inf"
}
(2) nan
(3) inf
(4) -inf
(5)
{
"field1": "NaN",
"field2": "Inf",
"field3": "-Inf"
}
#include <jsoncons/json_cursor.hpp>
#include <jsoncons/json_encoder.hpp>
#include <fstream>
#include <cassert>
int main()
{
std::ofstream os("./output/book_catalog.json",
std::ios_base::out | std::ios_base::trunc);
assert(os);
compact_json_stream_encoder encoder(os); // no indent
encoder.begin_array();
encoder.begin_object();
encoder.key("author");
encoder.string_value("Haruki Murakami");
encoder.key("title");
encoder.string_value("Hard-Boiled Wonderland and the End of the World");
encoder.key("price");
encoder.double_value(18.9);
encoder.end_object();
encoder.begin_object();
encoder.key("author");
encoder.string_value("Graham Greene");
encoder.key("title");
encoder.string_value("The Comedians");
encoder.key("price");
encoder.double_value(15.74);
encoder.end_object();
encoder.end_array();
encoder.flush();
os.close();
// Read the JSON and write it prettified to std::cout
json_stream_encoder writer(std::cout); // indent
std::ifstream is("./output/book_catalog.json");
assert(is);
json_reader reader(is, writer);
reader.read();
std::cout << "\n\n";
}
Output:
[
{
"author": "Haruki Murakami",
"title": "Hard-Boiled Wonderland and the End of the World",
"price": 18.9
},
{
"author": "Graham Greene",
"title": "The Comedians",
"price": 15.74
}
]
A typical pull parsing application will repeatedly process the current()
event and call next()
to advance to the next event, until done()
returns true
.
#include <jsoncons/json_cursor.hpp>
#include <fstream>
int main()
{
std::ifstream is("./output/book_catalog.json");
json_cursor cursor(is);
for (; !cursor.done(); cursor.next())
{
const auto& event = cursor.current();
switch (event.event_type())
{
case staj_event_type::begin_array:
std::cout << event.event_type() << " " << "\n";
break;
case staj_event_type::end_array:
std::cout << event.event_type() << " " << "\n";
break;
case staj_event_type::begin_object:
std::cout << event.event_type() << " " << "\n";
break;
case staj_event_type::end_object:
std::cout << event.event_type() << " " << "\n";
break;
case staj_event_type::key:
// Or std::string_view, if supported
std::cout << event.event_type() << ": " << event.get<jsoncons::string_view>() << "\n";
break;
case staj_event_type::string_value:
// Or std::string_view, if supported
std::cout << event.event_type() << ": " << event.get<jsoncons::string_view>() << "\n";
break;
case staj_event_type::null_value:
std::cout << event.event_type() << "\n";
break;
case staj_event_type::bool_value:
std::cout << event.event_type() << ": " << std::boolalpha << event.get<bool>() << "\n";
break;
case staj_event_type::int64_value:
std::cout << event.event_type() << ": " << event.get<int64_t>() << "\n";
break;
case staj_event_type::uint64_value:
std::cout << event.event_type() << ": " << event.get<uint64_t>() << "\n";
break;
case staj_event_type::double_value:
std::cout << event.event_type() << ": " << event.get<double>() << "\n";
break;
default:
std::cout << "Unhandled event type: " << event.event_type() << " " << "\n";
break;
}
}
}
Output:
begin_array
begin_object
key: author
string_value: Haruki Murakami
key: title
string_value: Hard-Boiled Wonderland and the End of the World
key: price
double_value: 18.9
end_object
begin_object
key: author
string_value: Graham Greene
key: title
string_value: The Comedians
key: price
double_value: 15.74
end_object
end_array
You can apply a filter to a cursor using the pipe syntax (e.g., cursor | filter1 | filter2 | ...
)
#include <jsoncons/json_cursor.hpp>
#include <fstream>
// Filter out all events except names of authors
int main()
{
bool author_next = false;
auto filter = [&](const staj_event& event, const ser_context&) -> bool
{
if (event.event_type() == staj_event_type::key &&
event.get<jsoncons::string_view>() == "author")
{
author_next = true;
return false;
}
if (author_next)
{
author_next = false;
return true;
}
return false;
};
std::ifstream is("./output/book_catalog.json");
json_cursor cursor(is);
auto filtered_c = cursor | filter;
for (; !filtered_c.done(); filtered_c.next())
{
const auto& event = filtered_c.current();
switch (event.event_type())
{
case staj_event_type::string_value:
std::cout << event.get<jsoncons::string_view>() << "\n";
break;
default:
std::cout << "Unhandled event type: " << event.event_type() << " " << "\n";
break;
}
}
}
Output:
Haruki Murakami
Graham Greene
When positioned on a begin_object
event,
the read_to
function can pull a complete object representing
the events from begin_object
to end_object
,
and when positioned on a begin_array
event, a complete array
representing the events from begin_array
ro end_array
.
#include <jsoncons/json.hpp> // json_decoder and json
#include <fstream>
int main()
{
std::ifstream is("./output/book_catalog.json");
json_cursor cursor(is);
json_decoder<json> decoder;
for (; !cursor.done(); cursor.next())
{
const auto& event = cursor.current();
switch (event.event_type())
{
case staj_event_type::begin_array:
{
std::cout << event.event_type() << " " << "\n";
break;
}
case staj_event_type::end_array:
{
std::cout << event.event_type() << " " << "\n";
break;
}
case staj_event_type::begin_object:
{
std::cout << event.event_type() << " " << "\n";
cursor.read_to(decoder);
json j = decoder.get_result();
std::cout << pretty_print(j) << "\n";
break;
}
default:
{
std::cout << "Unhandled event type: " << event.event_type() << " " << "\n";
break;
}
}
}
}
Output:
begin_array
begin_object
{
"author": "Haruki Murakami",
"price": 18.9,
"title": "Hard-Boiled Wonderland and the End of the World"
}
begin_object
{
"author": "Graham Greene",
"price": 15.74,
"title": "The Comedians"
}
end_array
#include <jsoncons/json.hpp>
#include <fstream>
int main()
{
std::ifstream is("./output/book_catalog.json");
json_cursor cursor(is);
auto view = staj_array<json>(cursor);
for (const auto& j : view)
{
std::cout << pretty_print(j) << "\n";
}
}
Output:
{
"author": "Haruki Murakami",
"price": 18.9,
"title": "Hard-Boiled Wonderland and the End of the World"
}
{
"author": "Graham Greene",
"price": 15.74,
"title": "The Comedians"
}
#include <jsoncons/json.hpp>
#include <fstream>
namespace ns {
struct book
{
std::string author;
std::string title;
double price;
};
} // namespace ns
JSONCONS_ALL_MEMBER_TRAITS(ns::book,author,title,price)
int main()
{
std::ifstream is("./output/book_catalog.json");
json_cursor cursor(is);
auto view = staj_array<ns::book>(cursor);
for (const auto& book : view)
{
std::cout << book.author << ", " << book.title << "\n";
}
}
Output:
Haruki Murakami, Hard-Boiled Wonderland and the End of the World
Graham Greene, The Comedians
#include <jsoncons/json.hpp>
namespace ns {
enum class BookCategory {fiction,biography};
inline
std::ostream& operator<<(std::ostream& os, const BookCategory& category)
{
switch (category)
{
case BookCategory::fiction: os << "fiction, "; break;
case BookCategory::biography: os << "biography, "; break;
}
return os;
}
// #1 Class with public member data and default constructor
struct Book1
{
BookCategory category;
std::string author;
std::string title;
double price;
};
// #2 Class with private member data and default constructor
class Book2
{
BookCategory category;
std::string author;
std::string title;
double price;
Book2() = default;
JSONCONS_TYPE_TRAITS_FRIEND
public:
BookCategory get_category() const {return category;}
const std::string& get_author() const {return author;}
const std::string& get_title() const{return title;}
double get_price() const{return price;}
};
// #3 Class with getters and initializing constructor
class Book3
{
BookCategory category_;
std::string author_;
std::string title_;
double price_;
public:
Book3(BookCategory category,
const std::string& author,
const std::string& title,
double price)
: category_(category), author_(author), title_(title), price_(price)
{
}
BookCategory category() const {return category_;}
const std::string& author() const{return author_;}
const std::string& title() const{return title_;}
double price() const{return price_;}
};
// #4 Class with getters and setters
class Book4
{
BookCategory category_;
std::string author_;
std::string title_;
double price_;
public:
Book4()
: price_(0)
{
}
Book4(BookCategory category,
const std::string& author,
const std::string& title,
double price)
: category_(category), author_(author), title_(title), price_(price)
{
}
BookCategory get_category() const
{
return category_;
}
void set_category(BookCategory value)
{
category_ = value;
}
const std::string& get_author() const
{
return author_;
}
void set_author(const std::string& value)
{
author_ = value;
}
const std::string& get_title() const
{
return title_;
}
void set_title(const std::string& value)
{
title_ = value;
}
double get_price() const
{
return price_;
}
void set_price(double value)
{
price_ = value;
}
};
} // namespace ns
// Declare the traits at global scope
JSONCONS_ENUM_TRAITS(ns::BookCategory,fiction,biography)
JSONCONS_ALL_MEMBER_TRAITS(ns::Book1,category,author,title,price)
JSONCONS_ALL_MEMBER_TRAITS(ns::Book2,category,author,title,price)
JSONCONS_ALL_CTOR_GETTER_TRAITS(ns::Book3,category,author,title,price)
JSONCONS_ALL_GETTER_SETTER_TRAITS(ns::Book4,get_,set_,category,author,title,price)
using namespace jsoncons; // for convenience
int main()
{
const std::string input = R"(
[
{
"category" : "fiction",
"author" : "Haruki Murakami",
"title" : "Kafka on the Shore",
"price" : 25.17
},
{
"category" : "biography",
"author" : "Robert A. Caro",
"title" : "The Path to Power: The Years of Lyndon Johnson I",
"price" : 16.99
}
]
)";
std::cout << "(1)\n\n";
auto books1 = decode_json<std::vector<ns::Book1>>(input);
for (const auto& item : books1)
{
std::cout << item.category << ", "
<< item.author << ", "
<< item.title << ", "
<< item.price << "\n";
}
std::cout << "\n";
encode_json_pretty(books1, std::cout);
std::cout << "\n\n";
std::cout << "(2)\n\n";
auto books2 = decode_json<std::vector<ns::Book2>>(input);
for (const auto& item : books2)
{
std::cout << item.get_category() << ", "
<< item.get_author() << ", "
<< item.get_title() << ", "
<< item.get_price() << "\n";
}
std::cout << "\n";
encode_json_pretty(books2, std::cout);
std::cout << "\n\n";
std::cout << "(3)\n\n";
auto books3 = decode_json<std::vector<ns::Book3>>(input);
for (const auto& item : books3)
{
std::cout << item.category() << ", "
<< item.author() << ", "
<< item.title() << ", "
<< item.price() << "\n";
}
std::cout << "\n";
encode_json_pretty(books3, std::cout);
std::cout << "\n\n";
std::cout << "(4)\n\n";
auto books4 = decode_json<std::vector<ns::Book4>>(input);
for (const auto& item : books4)
{
std::cout << item.get_category() << ", "
<< item.get_author() << ", "
<< item.get_title() << ", "
<< item.get_price() << "\n";
}
std::cout << "\n";
encode_json_pretty(books4, std::cout);
std::cout << "\n\n";
}
Output:
(1)
fiction, Haruki Murakami, Kafka on the Shore, 25.170000
biography, Robert A. Caro, The Path to Power: The Years of Lyndon Johnson I, 16.990000
[
{
"author": "Haruki Murakami",
"category": "fiction",
"price": 25.17,
"title": "Kafka on the Shore"
},
{
"author": "Robert A. Caro",
"category": "biography",
"price": 16.99,
"title": "The Path to Power: The Years of Lyndon Johnson I"
}
]
The output for (2), (3) and (4) is the same.
#include <jsoncons/json.hpp>
namespace ns {
enum class BookCategory {fiction,biography};
inline
std::ostream& operator<<(std::ostream& os, const BookCategory& category)
{
switch (category)
{
case BookCategory::fiction: os << "fiction, "; break;
case BookCategory::biography: os << "biography, "; break;
}
return os;
}
// #1 Class with public member data and default constructor
struct Book1
{
BookCategory category;
std::string author;
std::string title;
double price;
};
// #2 Class with private member data and default constructor
class Book2
{
BookCategory category_;
std::string author_;
std::string title_;
double price_;
Book2() = default;
JSONCONS_TYPE_TRAITS_FRIEND
public:
BookCategory category() const {return category_;}
const std::string& author() const {return author_;}
const std::string& title() const{return title_;}
double price() const{return price_;}
};
// #3 Class with getters and initializing constructor
class Book3
{
BookCategory category_;
std::string author_;
std::string title_;
double price_;
public:
Book3(BookCategory category,
const std::string& author,
const std::string& title,
double price)
: category_(category), author_(author), title_(title), price_(price)
{
}
BookCategory category() const {return category_;}
const std::string& author() const{return author_;}
const std::string& title() const{return title_;}
double price() const{return price_;}
};
// #4 Class with getters, setters and default constructor
class Book4
{
BookCategory category_;
std::string author_;
std::string title_;
double price_;
public:
BookCategory getCategory() const {return category_;}
void setCategory(const BookCategory& value) {category_ = value;}
const std::string& getAuthor() const {return author_;}
void setAuthor(const std::string& value) {author_ = value;}
const std::string& getTitle() const {return title_;}
void setTitle(const std::string& value) {title_ = value;}
double getPrice() const {return price_;}
void setPrice(double value) {price_ = value;}
};
} // namespace ns
// Declare the traits at global scope
JSONCONS_ENUM_NAME_TRAITS(ns::BookCategory,(fiction,"Fiction"),(biography,"Biography"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Book1,(category,"Category"),(author,"Author"),
(title,"Title"),(price,"Price"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Book2,(category_,"Category"),(author_,"Author"),
(title_,"Title"),(price_,"Price"))
JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ns::Book3,(category,"Category"),(author,"Author"),
(title,"Title"),(price,"Price"))
JSONCONS_ALL_GETTER_SETTER_NAME_TRAITS(ns::Book4,(getCategory,setCategory,"Category"),
(getAuthor,setAuthor,"Author"),
(getTitle,setTitle,"Title"),
(getPrice,setPrice,"Price"))
using namespace jsoncons; // for convenience
int main()
{
const std::string input = R"(
[
{
"Category" : "Fiction",
"Author" : "Haruki Murakami",
"Title" : "Kafka on the Shore",
"Price" : 25.17
},
{
"Category" : "Biography",
"Author" : "Robert A. Caro",
"Title" : "The Path to Power: The Years of Lyndon Johnson I",
"Price" : 16.99
}
]
)";
std::cout << "(1)\n\n";
auto books1 = decode_json<std::vector<ns::Book1>>(input);
for (const auto& item : books1)
{
std::cout << item.category << ", "
<< item.author << ", "
<< item.title << ", "
<< item.price << "\n";
}
std::cout << "\n";
encode_json_pretty(books1, std::cout);
std::cout << "\n\n";
std::cout << "(2)\n\n";
auto books2 = decode_json<std::vector<ns::Book2>>(input);
for (const auto& item : books2)
{
std::cout << item.category() << ", "
<< item.author() << ", "
<< item.title() << ", "
<< item.price() << "\n";
}
std::cout << "\n";
encode_json_pretty(books2, std::cout);
std::cout << "\n\n";
std::cout << "(3)\n\n";
auto books3 = decode_json<std::vector<ns::Book3>>(input);
for (const auto& item : books3)
{
std::cout << item.category() << ", "
<< item.author() << ", "
<< item.title() << ", "
<< item.price() << "\n";
}
std::cout << "\n";
encode_json_pretty(books3, std::cout);
std::cout << "\n\n";
std::cout << "(4)\n\n";
auto books4 = decode_json<std::vector<ns::Book4>>(input);
for (const auto& item : books4)
{
std::cout << item.getCategory() << ", "
<< item.getAuthor() << ", "
<< item.getTitle() << ", "
<< item.getPrice() << "\n";
}
std::cout << "\n";
encode_json_pretty(books4, std::cout);
std::cout << "\n\n";
}
Output:
(1)
fiction, Haruki Murakami, Kafka on the Shore, 25.170000
biography, Robert A. Caro, The Path to Power: The Years of Lyndon Johnson I, 16.990000
[
{
"Author": "Haruki Murakami",
"Category": "Fiction",
"Price": 25.17,
"Title": "Kafka on the Shore"
},
{
"Author": "Robert A. Caro",
"Category": "Biography",
"Price": 16.99,
"Title": "The Path to Power: The Years of Lyndon Johnson I"
}
]
The output for (2), (3) and (4) is the same.
JSONCONS_N_MEMBER_TRAITS
and JSONCONS_ALL_MEMBER_TRAITS
both generate
the code to specialize json_type_traits
from member data. The difference is that JSONCONS_N_MEMBER_TRAITS
does not require all member names to be present in the JSON data, while JSONCONS_ALL_MEMBER_TRAITS
does.
More generaly, the qualifier N in the macro name indicates that only a specified number of members
must be present in the JSON.
#include <iostream>
#include <jsoncons/json.hpp>
#include <vector>
#include <string>
namespace ns {
class Person
{
public:
Person(const std::string& name, const std::string& surname,
const std::string& ssn, unsigned int age)
: name(name), surname(surname), ssn(ssn), age(age) { }
private:
// Make json_type_traits specializations friends to give accesses to private members
JSONCONS_TYPE_TRAITS_FRIEND
Person() : age(0) {}
std::string name;
std::string surname;
std::string ssn;
unsigned int age;
};
} // namespace ns
// Declare the traits. Specify which data members need to be serialized, and how many are mandatory.
JSONCONS_N_MEMBER_TRAITS(ns::Person, 2, name, surname, ssn, age)
int main()
{
try
{
// Incomplete JSON data: field ssn missing
std::string data = R"({"name":"Rod","surname":"Bro","age":30})";
auto person = jsoncons::decode_json<ns::Person>(data);
std::string s;
jsoncons::encode_json_pretty(person, s);
std::cout << s << "\n";
}
catch (const std::exception& e)
{
std::cout << e.what() << "";
}
}
Output:
{
"age": 30,
"name": "Rod",
"ssn": "",
"surname": "Bro"
}
If all members of the JSON data must be present, use
JSONCONS_ALL_MEMBER_TRAITS(ns::Person, name, surname, ssn, age)
instead. This will cause an exception to be thrown with the message
Key 'ssn' not found
jsoncons supports conversion between JSON text and C++ data structures. The functions decode_json
and encode_json convert JSON formatted strings or streams to C++ data structures and back.
Decode and encode work for all C++ classes that have
json_type_traits
defined. jsoncons already supports many types in the standard library,
and your own types will be supported too if you specialize json_type_traits
in the jsoncons
namespace.
#include <iostream>
#include <jsoncons/json.hpp>
#include <vector>
#include <string>
namespace ns {
struct book
{
std::string author;
std::string title;
double price;
};
} // namespace ns
namespace jsoncons {
template<class Json>
struct json_type_traits<Json, ns::book>
{
using allocator_type = typename Json::allocator_type;
static bool is(const Json& j) noexcept
{
return j.is_object() && j.contains("author") &&
j.contains("title") && j.contains("price");
}
static ns::book as(const Json& j)
{
ns::book val;
val.author = j.at("author").template as<std::string>();
val.title = j.at("title").template as<std::string>();
val.price = j.at("price").template as<double>();
return val;
}
static Json to_json(const ns::book& val,
allocator_type allocator=allocator_type())
{
Json j(allocator);
j.try_emplace("author", val.author);
j.try_emplace("title", val.title);
j.try_emplace("price", val.price);
return j;
}
};
} // namespace jsoncons
To save typing and enhance readability, the jsoncons library defines macros, so you could also write
JSONCONS_ALL_MEMBER_TRAITS(ns::book, author, title, price)
which expands to the code above.
using namespace jsoncons; // for convenience
int main()
{
const std::string s = R"(
[
{
"author" : "Haruki Murakami",
"title" : "Kafka on the Shore",
"price" : 25.17
},
{
"author" : "Charles Bukowski",
"title" : "Pulp",
"price" : 22.48
}
]
)";
std::vector<ns::book> book_list = decode_json<std::vector<ns::book>>(s);
std::cout << "(1)\n";
for (const auto& item : book_list)
{
std::cout << item.author << ", "
<< item.title << ", "
<< item.price << "\n";
}
std::cout << "\n(2)\n";
encode_json_pretty(book_list, std::cout);
std::cout << "\n\n";
}
Output:
(1)
Haruki Murakami, Kafka on the Shore, 25.17
Charles Bukowski, Pulp, 22.48
(2)
[
{
"author": "Haruki Murakami",
"price": 25.17,
"title": "Kafka on the Shore"
},
{
"author": "Charles Bukowski",
"price": 22.48,
"title": "Pulp"
}
]
The jsoncons library includes a json_type_traits specialization for
jsoncons::optional<T>
if T
is also specialized. jsoncons::optional<T>
is aliased to
std::optional if
jsoncons detects the presence of C++17, or if JSONCONS_HAS_STD_OPTIONAL
is defined.
An empty jsoncons::optional<T>
value correspond to JSON null.
This example assumes C++17 language support (otherwise substitute jsoncons::optional
.)
Macro names include qualifiers _ALL_
or _N_
to indicate that the generated traits require all
members be present in the JSON, or a specified number be present. For non-mandatory members, the generated
traits to_json
function will exclude altogether empty values for std::optional
.
#include <cassert>
#include <jsoncons/json.hpp>
namespace ns
{
class MetaDataReplyTest
{
public:
MetaDataReplyTest()
: description()
{
}
const std::string& GetStatus() const
{
return status;
}
const std::string& GetPayload() const
{
return payload;
}
const std::optional<std::string>& GetDescription() const
{
return description;
}
private:
JSONCONS_TYPE_TRAITS_FRIEND
std::string status;
std::string payload;
std::optional<std::string> description;
};
}
JSONCONS_N_MEMBER_TRAITS(ns::MetaDataReplyTest, 2, status, payload, description)
using namespace jsoncons;
int main()
{
std::string input1 = R"({
"status": "OK",
"payload": "Modified",
"description": "TEST"
})";
std::string input2 = R"({
"status": "OK",
"payload": "Modified"
})";
auto val1 = decode_json<ns::MetaDataReplyTest>(input1);
assert(val1.GetStatus() == "OK");
assert(val1.GetPayload() == "Modified");
assert(val1.GetDescription());
assert(val1.GetDescription() == "TEST");
auto val2 = decode_json<ns::MetaDataReplyTest>(input2);
assert(val2.GetStatus() == "OK");
assert(val2.GetPayload() == "Modified");
assert(!val2.GetDescription());
std::string output1;
std::string output2;
encode_json_pretty(val2,output2);
encode_json_pretty(val1,output1);
std::cout << "(1)\n";
std::cout << output1 << "\n\n";
std::cout << "(2)\n";
std::cout << output2 << "\n\n";
}
Output:
(1)
{
"description": "TEST",
"payload": "Modified",
"status": "OK"
}
(2)
{
"payload": "Modified",
"status": "OK"
}
The jsoncons library includes json_type_traits specializations for
std::shared_ptr<T>
and std::unique_ptr<T>
if T
is not a polymorphic class,
i.e., does not have any virtual functions, and if T
is also specialized. Empty std::shared_ptr<T>
and std::unique_ptr<T>
values correspond to JSON null.
In addition, users can implement json_type_traits
for std::shared_ptr
and std::unique_ptr
with polymorphic classes using the convenience macro JSONCONS_POLYMORPHIC_TRAITS
, or by specializing json_type_traits
explicitly.
The convenience macros whose names include the qualifier _N_
do not require all members to be present in the JSON.
For these, the generated traits to_json
function will exclude altogether empty values
for std::shared_ptr
and std::unique_ptr
.
namespace ns {
struct smart_pointer_test
{
std::shared_ptr<std::string> field1;
std::unique_ptr<std::string> field2;
std::shared_ptr<std::string> field3;
std::unique_ptr<std::string> field4;
std::shared_ptr<std::string> field5;
std::unique_ptr<std::string> field6;
std::shared_ptr<std::string> field7;
std::unique_ptr<std::string> field8;
};
} // namespace ns
// Declare the traits, first 4 members mandatory, last 4 non-mandatory
JSONCONS_N_MEMBER_TRAITS(ns::smart_pointer_test,4,field1,field2,field3,field4,field5,field6,field7,field8)
int main()
{
ns::smart_pointer_test val;
val.field1 = std::make_shared<std::string>("Field 1");
val.field2 = jsoncons::make_unique<std::string>("Field 2");
val.field3 = std::shared_ptr<std::string>(nullptr);
val.field4 = std::unique_ptr<std::string>(nullptr);
val.field5 = std::make_shared<std::string>("Field 5");
val.field6 = jsoncons::make_unique<std::string>("Field 6");
val.field7 = std::shared_ptr<std::string>(nullptr);
val.field8 = std::unique_ptr<std::string>(nullptr);
std::string buf;
encode_json_pretty(val, buf);
std::cout << buf << "\n";
auto other = decode_json<ns::smart_pointer_test>(buf);
assert(*other.field1 == *val.field1);
assert(*other.field2 == *val.field2);
assert(!other.field3);
assert(!other.field4);
assert(*other.field5 == *val.field5);
assert(*other.field6 == *val.field6);
assert(!other.field7);
assert(!other.field8);
}
Output:
{
"field1": "Field 1",
"field2": "Field 2",
"field3": null,
"field4": null,
"field5": "Field 5",
"field6": "Field 6"
}
#include <cassert>
#include <jsoncons/json.hpp>
namespace ns {
template <typename T1, typename T2>
struct TemplatedStruct
{
T1 aT1;
T2 aT2;
friend bool operator==(const TemplatedStruct& lhs, const TemplatedStruct& rhs)
{
return lhs.aT1 == rhs.aT1 && lhs.aT2 == rhs.aT2;
}
friend bool operator!=(const TemplatedStruct& lhs, const TemplatedStruct& rhs)
{
return !(lhs == rhs);
}
};
} // namespace ns
// Declare the traits. Specify the number of template parameters and which data members need to be serialized.
JSONCONS_TPL_ALL_MEMBER_TRAITS(2,ns::TemplatedStruct,aT1,aT2)
using namespace jsoncons; // for convenience
int main()
{
using value_type = ns::TemplatedStruct<int,std::wstring>;
value_type val{1, L"sss"};
std::wstring s;
encode_json(val, s);
auto val2 = decode_json<value_type>(s);
assert(val2 == val);
}
This example makes use of the convenience macros JSONCONS_ENUM_TRAITS
and JSONCONS_ALL_CTOR_GETTER_TRAITS
to specialize the
json_type_traits for the enum type
ns::hiking_experience
and the classes ns::hiking_reputon
and
ns::hiking_reputation
.
The macro JSONCONS_ENUM_TRAITS
generates the code from
the enum values, and the macro JSONCONS_ALL_CTOR_GETTER_TRAITS
generates the code from the get functions and a constructor.
These macro declarations must be placed outside any namespace blocks.
namespace ns {
enum class hiking_experience {beginner,intermediate,advanced};
class hiking_reputon
{
std::string rater_;
hiking_experience assertion_;
std::string rated_;
double rating_;
std::optional<std::chrono::seconds> generated_; // assumes C++17, if not use jsoncons::optional
std::optional<std::chrono::seconds> expires_;
public:
hiking_reputon(const std::string& rater,
hiking_experience assertion,
const std::string& rated,
double rating,
const std::optional<std::chrono::seconds>& generated = std::optional<std::chrono::seconds>(),
const std::optional<std::chrono::seconds>& expires = std::optional<std::chrono::seconds>())
: rater_(rater), assertion_(assertion), rated_(rated), rating_(rating),
generated_(generated), expires_(expires)
{
}
const std::string& rater() const {return rater_;}
hiking_experience assertion() const {return assertion_;}
const std::string& rated() const {return rated_;}
double rating() const {return rating_;}
std::optional<std::chrono::seconds> generated() const {return generated_;}
std::optional<std::chrono::seconds> expires() const {return expires_;}
friend bool operator==(const hiking_reputon& lhs, const hiking_reputon& rhs)
{
return lhs.rater_ == rhs.rater_ && lhs.assertion_ == rhs.assertion_ &&
lhs.rated_ == rhs.rated_ && lhs.rating_ == rhs.rating_ &&
lhs.confidence_ == rhs.confidence_ && lhs.expires_ == rhs.expires_;
}
friend bool operator!=(const hiking_reputon& lhs, const hiking_reputon& rhs)
{
return !(lhs == rhs);
};
};
class hiking_reputation
{
std::string application_;
std::vector<hiking_reputon> reputons_;
public:
hiking_reputation(const std::string& application,
const std::vector<hiking_reputon>& reputons)
: application_(application),
reputons_(reputons)
{}
const std::string& application() const { return application_;}
const std::vector<hiking_reputon>& reputons() const { return reputons_;}
};
} // namespace ns
// Declare the traits. Specify which data members need to be serialized.
JSONCONS_ENUM_TRAITS(ns::hiking_experience, beginner, intermediate, advanced)
// First four members listed are mandatory, generated and expires are optional
JSONCONS_N_CTOR_GETTER_TRAITS(ns::hiking_reputon, 4, rater, assertion, rated, rating,
generated, expires)
// All members are mandatory
JSONCONS_ALL_CTOR_GETTER_TRAITS(ns::hiking_reputation, application, reputons)
int main()
{
// Decode the string of data into a c++ structure
ns::hiking_reputation v = decode_json<ns::hiking_reputation>(data);
// Iterate over reputons array value
std::cout << "(1)\n";
for (const auto& item : v.reputons())
{
std::cout << item.rated() << ", " << item.rating();
if (item.generated())
{
std::cout << ", " << (*item.generated()).count();
}
std::cout << "\n";
}
// Encode the c++ structure into a string
std::string s;
encode_json_pretty(v, s);
std::cout << "(2)\n";
std::cout << s << "\n";
}
Output:
(1)
Marilyn C, 0.9, 1514862245
(2)
{
"application": "hiking",
"reputons": [
{
"assertion": "advanced",
"generated": 1514862245,
"rated": "Marilyn C",
"rater": "HikingAsylum",
"rating": 0.9
}
]
}
This example uses the convenience macro JSONCONS_N_CTOR_GETTER_TRAITS
to generate the json_type_traits boilerplate for the HourlyEmployee
and CommissionedEmployee
derived classes, and JSONCONS_POLYMORPHIC_TRAITS
to generate the json_type_traits
boilerplate
for std::shared_ptr<Employee>
and std::unique_ptr<Employee>
. The type selection strategy is based
on the presence of mandatory members, in particular, to the firstName
, lastName
, and wage
members of an
HourlyEmployee
, and to the firstName
, lastName
, baseSalary
, and commission
members of a CommissionedEmployee
.
Non-mandatory members are not considered for the purpose of type selection.
#include <cassert>
#include <iostream>
#include <vector>
#include <jsoncons/json.hpp>
using namespace jsoncons;
namespace ns {
class Employee
{
std::string firstName_;
std::string lastName_;
public:
Employee(const std::string& firstName, const std::string& lastName)
: firstName_(firstName), lastName_(lastName)
{
}
virtual ~Employee() noexcept = default;
virtual double calculatePay() const = 0;
const std::string& firstName() const {return firstName_;}
const std::string& lastName() const {return lastName_;}
};
class HourlyEmployee : public Employee
{
double wage_;
unsigned hours_;
public:
HourlyEmployee(const std::string& firstName, const std::string& lastName,
double wage, unsigned hours)
: Employee(firstName, lastName),
wage_(wage), hours_(hours)
{
}
double wage() const {return wage_;}
unsigned hours() const {return hours_;}
double calculatePay() const override
{
return wage_*hours_;
}
};
class CommissionedEmployee : public Employee
{
double baseSalary_;
double commission_;
unsigned sales_;
public:
CommissionedEmployee(const std::string& firstName, const std::string& lastName,
double baseSalary, double commission, unsigned sales)
: Employee(firstName, lastName),
baseSalary_(baseSalary), commission_(commission), sales_(sales)
{
}
double baseSalary() const
{
return baseSalary_;
}
double commission() const
{
return commission_;
}
unsigned sales() const
{
return sales_;
}
double calculatePay() const override
{
return baseSalary_ + commission_*sales_;
}
};
} // namespace ns
JSONCONS_N_CTOR_GETTER_TRAITS(ns::HourlyEmployee, 3, firstName, lastName, wage, hours)
JSONCONS_N_CTOR_GETTER_TRAITS(ns::CommissionedEmployee, 4, firstName, lastName, baseSalary, commission, sales)
JSONCONS_POLYMORPHIC_TRAITS(ns::Employee, ns::HourlyEmployee, ns::CommissionedEmployee)
int main()
{
std::string input = R"(
[
{
"firstName": "John",
"hours": 1000,
"lastName": "Smith",
"wage": 40.0
},
{
"baseSalary": 30000.0,
"commission": 0.25,
"firstName": "Jane",
"lastName": "Doe",
"sales": 1000
}
]
)";
auto v = decode_json<std::vector<std::unique_ptr<ns::Employee>>>(input);
std::cout << "(1)\n";
for (const auto& p : v)
{
std::cout << p->firstName() << " " << p->lastName() << ", " << p->calculatePay() << "\n";
}
std::cout << "\n(2)\n";
encode_json_pretty(v, std::cout);
std::cout << "\n\n(3)\n";
json j(v);
std::cout << pretty_print(j) << "\n\n";
}
Output:
(1)
John Smith, 40000
Jane Doe, 30250
(2)
[
{
"firstName": "John",
"hours": 1000,
"lastName": "Smith",
"wage": 40.0
},
{
"baseSalary": 30000.0,
"commission": 0.25,
"firstName": "Jane",
"lastName": "Doe",
"sales": 1000
}
]
(3)
[
{
"firstName": "John",
"hours": 1000,
"lastName": "Smith",
"wage": 40.0
},
{
"baseSalary": 30000.0,
"commission": 0.25,
"firstName": "Jane",
"lastName": "Doe",
"sales": 1000
}
]
When deserializing a polymorphic type, jsoncons needs to know how
to convert a json value to the proper derived class. In the Employee
example above, the type selection strategy is based
on the presence of mandatory members in the derived classes. If
derived classes cannot be distinguished in this way,
you can introduce extra members. The convenience
macros JSONCONS_N_MEMBER_TRAITS
, JSONCONS_ALL_MEMBER_TRAITS
,
JSONCONS_TPL_N_MEMBER_TRAITS
, JSONCONS_TPL_ALL_MEMBER_TRAITS
,
JSONCONS_N_MEMBER_NAME_TRAITS
, JSONCONS_ALL_MEMBER_NAME_TRAITS
,
JSONCONS_TPL_N_MEMBER_NAME_TRAITS
, and JSONCONS_TPL_ALL_MEMBER_NAME_TRAITS
allow you to have const
or static const
data members that are serialized and that
particpate in the type selection strategy during deserialization.
namespace ns {
class Foo
{
public:
virtual ~Foo() noexcept = default;
};
class Bar : public Foo
{
static const bool bar = true;
JSONCONS_TYPE_TRAITS_FRIEND
};
class Baz : public Foo
{
static const bool baz = true;
JSONCONS_TYPE_TRAITS_FRIEND
};
} // ns
JSONCONS_N_MEMBER_TRAITS(ns::Bar,1,bar)
JSONCONS_N_MEMBER_TRAITS(ns::Baz,1,baz)
JSONCONS_POLYMORPHIC_TRAITS(ns::Foo, ns::Bar, ns::Baz)
int main()
{
std::vector<std::unique_ptr<ns::Foo>> u;
u.emplace_back(new ns::Bar());
u.emplace_back(new ns::Baz());
std::string buffer;
encode_json(u, buffer);
std::cout << "(1)\n" << buffer << "\n\n";
auto v = decode_json<std::vector<std::unique_ptr<ns::Foo>>>(buffer);
std::cout << "(2)\n";
for (const auto& ptr : v)
{
if (dynamic_cast<ns::Bar*>(ptr.get()))
{
std::cout << "A bar\n";
}
else if (dynamic_cast<ns::Baz*>(ptr.get()))
{
std::cout << "A baz\n";
}
}
}
Output:
(1)
[{"bar":true},{"baz":true}]
(2)
A bar
A baz
This example assumes C++17 language support and jsoncons v0.154.0 or later.
#include <jsoncons/json.hpp>
namespace ns {
enum class Color {yellow, red, green, blue};
inline
std::ostream& operator<<(std::ostream& os, Color val)
{
switch (val)
{
case Color::yellow: os << "yellow"; break;
case Color::red: os << "red"; break;
case Color::green: os << "green"; break;
case Color::blue: os << "blue"; break;
}
return os;
}
class Fruit
{
private:
JSONCONS_TYPE_TRAITS_FRIEND
std::string name_;
Color color_;
public:
friend std::ostream& operator<<(std::ostream& os, const Fruit& val)
{
os << "name: " << val.name_ << ", color: " << val.color_ << "\n";
return os;
}
};
class Fabric
{
private:
JSONCONS_TYPE_TRAITS_FRIEND
int size_;
std::string material_;
public:
friend std::ostream& operator<<(std::ostream& os, const Fabric& val)
{
os << "size: " << val.size_ << ", material: " << val.material_ << "\n";
return os;
}
};
class Basket {
private:
JSONCONS_TYPE_TRAITS_FRIEND
std::string owner_;
std::vector<std::variant<Fruit, Fabric>> items_;
public:
std::string owner() const
{
return owner_;
}
std::vector<std::variant<Fruit, Fabric>> items() const
{
return items_;
}
};
} // ns
} // namespace
JSONCONS_ENUM_NAME_TRAITS(ns::Color, (yellow, "YELLOW"), (red, "RED"), (green, "GREEN"), (blue, "BLUE"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Fruit,
(name_, "name"),
(color_, "color"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Fabric,
(size_, "size"),
(material_, "material"))
JSONCONS_ALL_MEMBER_NAME_TRAITS(ns::Basket,
(owner_, "owner"),
(items_, "items"))
int main()
{
std::string input = R"(
{
"owner": "Rodrigo",
"items": [
{
"name": "banana",
"color": "YELLOW"
},
{
"size": 40,
"material": "wool"
},
{
"name": "apple",
"color": "RED"
},
{
"size": 40,
"material": "cotton"
}
]
}
)";
ns::Basket basket = jsoncons::decode_json<ns::Basket>(input);
std::cout << basket.owner() << "\n\n";
std::cout << "(1)\n";
for (const auto& var : basket.items())
{
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, ns::Fruit>)
std::cout << "Fruit " << arg << '\n';
else if constexpr (std::is_same_v<T, ns::Fabric>)
std::cout << "Fabric " << arg << '\n';
}, var);
}
std::string output;
jsoncons::encode_json_pretty(basket, output);
std::cout << "(2)\n" << output << "\n\n";
}
Output:
Rodrigo
(1)
Fruit name: banana, color: yellow
Fabric size: 28, material: wool
Fruit name: apple, color: red
Fabric size: 28, material: cotton
(2)
{
"items": [
{
"color": "YELLOW",
"name": "banana"
},
{
"material": "wool",
"size": 40
},
{
"color": "RED",
"name": "apple"
},
{
"material": "cotton",
"size": 40
}
],
"owner": "Rodrigo"
}
For classes supported through the convenience macros, e.g. Fruit
and Fabric
from the previous example,
the type selection strategy is the same as for polymorphic types, and is based
on the presence of mandatory members in the classes. More generally,
the type selection strategy is based on the json_type_traits<Json,T>::is(const Json& j)
function, checking each type in the variant from left to right, and stopping when
json_type_traits<Json,T>::is(j)
returns true
.
Now consider
#include <jsoncons/json.hpp>
namespace ns {
enum class Color {yellow, red, green, blue};
} // ns
JSONCONS_ENUM_NAME_TRAITS(ns::Color, (yellow, "YELLOW"), (red, "RED"), (green, "GREEN"), (blue, "BLUE"))
int main()
{
using variant_type = std::variant<int, double, bool, std::string, ns::Color>;
std::vector<variant_type> vars = {100, 10.1, false, std::string("Hello World"), ns::Color::yellow};
std::string buffer;
jsoncons::encode_json_pretty(vars, buffer);
std::cout << "(1)\n" << buffer << "\n\n";
auto vars2 = jsoncons::decode_json<std::vector<variant_type>>(buffer);
auto visitor = [](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int " << arg << '\n';
else if constexpr (std::is_same_v<T, double>)
std::cout << "double " << arg << '\n';
else if constexpr (std::is_same_v<T, bool>)
std::cout << "bool " << arg << '\n';
else if constexpr (std::is_same_v<T, std::string>)
std::cout << "std::string " << arg << '\n';
else if constexpr (std::is_same_v<T, ns::Color>)
std::cout << "ns::Color " << arg << '\n';
};
std::cout << "(2)\n";
for (const auto& item : vars2)
{
std::visit(visitor, item);
}
std::cout << "\n";
}
Output:
(1)
[
100,
10.1,
false,
"Hello World",
"YELLOW"
]
(2)
int 100
double 10.1
bool false
std::string Hello World
std::string YELLOW
Encode is fine. But when decoding, jsoncons checks if the JSON string "YELLOW" is a std::string
before it checks whether it is an ns::Color
, and since the answer is yes,
it is stored in the variant as a std::string
.
But if we switch the order of ns::Color
and std::string
in the variant definition, viz.
using variant_type = std::variant<int, double, bool, ns::Color, std::string>;
strings containing the text "YELLOW", "RED", "GREEN", or "BLUE" are detected to be ns::Color
, and the others std::string
.
And the output becomes
(1)
[
100,
10.1,
false,
"Hello World",
"YELLOW"
]
(2)
int 100
double 10.1
bool false
std::string Hello World
ns::Color yellow
So: types that are more constrained should appear to the left of types that are less constrained.
#include <jsoncons/json.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
namespace jsoncons
{
template <class Json, class Backend>
struct json_type_traits<Json,boost::multiprecision::number<Backend>>
{
using multiprecision_type = boost::multiprecision::number<Backend>;
static bool is(const Json& val) noexcept
{
if (!(val.is_string() && val.semantic_tag() == semantic_tag::bigdec))
{
return false;
}
else
{
return true;
}
}
static multiprecision_type as(const Json& val)
{
return multiprecision_type(val.template as<std::string>());
}
static Json to_json(multiprecision_type val)
{
return Json(val.str(), semantic_tag::bigdec);
}
};
}
int main()
{
typedef boost::multiprecision::number<boost::multiprecision::cpp_dec_float_50> multiprecision_type;
std::string s = "[100000000000000000000000000000000.1234]";
json_options options;
options.lossless_number(true);
json j = json::parse(s, options);
multiprecision_type x = j[0].as<multiprecision_type>();
std::cout << "(1) " << std::setprecision(std::numeric_limits<multiprecision_type>::max_digits10)
<< x << "\n";
json j2(json_array_arg, {x});
std::cout << "(2) " << j2[0].as<std::string>() << "\n";
}
Output:
(1) 100000000000000000000000000000000.1234
(2) 100000000000000000000000000000000.1234
Start with an empty json object and insert some name-value pairs,
json image_sizing;
image_sizing.insert_or_assign("Resize To Fit",true); // a boolean
image_sizing.insert_or_assign("Resize Unit", "pixels"); // a string
image_sizing.insert_or_assign("Resize What", "long_edge"); // a string
image_sizing.insert_or_assign("Dimension 1",9.84); // a double
image_sizing.insert_or_assign("Dimension 2",json::null()); // a null value
or use an object initializer-list,
json file_settings( json_object_arg,{
{"Image Format", "JPEG"},
{"Color Space", "sRGB"},
{"Limit File Size", true},
{"Limit File Size To", 10000}
});
};
json color_spaces(json_array_arg); // an empty array
color_spaces.push_back("sRGB");
color_spaces.push_back("AdobeRGB");
color_spaces.push_back("ProPhoto RGB");
or use an array initializer-list,
json image_formats(json_array_arg, {"JPEG","PSD","TIFF","DNG"});
json cities(json_array_arg); // an empty array
cities.push_back("Toronto");
cities.push_back("Vancouver");
// Insert "Montreal" at beginning of array
cities.insert(cities.array_range().begin(),"Montreal");
std::cout << cities << std::endl;
Output:
["Montreal","Toronto","Vancouver"]
json::merge inserts another json object's key-value pairs into a json object, unless they already exist with an equivalent key.
json::merge_or_update inserts another json object's key-value pairs into a json object, or assigns them if they already exist.
The merge
and merge_or_update
functions perform only a one-level-deep shallow merge,
not a deep merge of nested objects.
json another = json::parse(R"(
{
"a" : "2",
"c" : [4,5,6]
}
)");
json j = json::parse(R"(
{
"a" : "1",
"b" : [1,2,3]
}
)");
j.merge(std::move(another));
std::cout << pretty_print(j) << std::endl;
Output:
{
"a": "1",
"b": [1,2,3],
"c": [4,5,6]
}
#include <jsoncons/json.hpp>
namespace jc=jsoncons;
int main()
{
std::vector<uint8_t> bytes = {'H','e','l','l','o'};
// default suggested encoding (base64url)
json j1(byte_string_arg, bytes);
std::cout << "(1) "<< j1 << "\n\n";
// base64 suggested encoding
json j2(byte_string_arg, bytes, semantic_tag::base64);
std::cout << "(2) "<< j2 << "\n\n";
// base16 suggested encoding
json j3(byte_string_arg, bytes, semantic_tag::base16);
std::cout << "(3) "<< j3 << "\n\n";
}
Output:
(1) "SGVsbG8"
(2) "SGVsbG8="
(3) "48656C6C6F"
Construct a 3-dimensional 4 x 3 x 2 json array with all elements initialized to 0.0:
json j = json::make_array<3>(4, 3, 2, 0.0);
double val = 1.0;
for (size_t i = 0; i < a.size(); ++i)
{
for (size_t j = 0; j < j[i].size(); ++j)
{
for (size_t k = 0; k < j[i][j].size(); ++k)
{
j[i][j][k] = val;
val += 1.0;
}
}
}
std::cout << pretty_print(j) << std::endl;
Output:
[
[
[1.0,2.0],
[3.0,4.0],
[5.0,6.0]
],
[
[7.0,8.0],
[9.0,10.0],
[11.0,12.0]
],
[
[13.0,14.0],
[15.0,16.0],
[17.0,18.0]
],
[
[19.0,20.0],
[21.0,22.0],
[23.0,24.0]
]
]
#include <jsoncons/json.hpp>
#include <iostream>
int main()
{
std::string input = R"(
{
"machines": [
{"id": 1, "state": "running"},
{"id": 2, "state": "stopped"},
{"id": 3, "state": "running"}
]
}
)";
json j = json::parse(input);
json j_v(json_array_arg);
for (const auto& item : j.at("machines").array_range())
{
if (item.at("state").as<std::string>() == "running")
{
j_v.emplace_back(json_const_pointer_arg, &item);
}
}
std::cout << "\n(1)\n" << pretty_print(j_v) << "\n\n";
for (const auto& item : j_v.array_range())
{
std::cout << "json type: " << item.type() << ", storage kind: " << item.storage() << "\n";
}
json j2 = deep_copy(j_v);
std::cout << "\n(2)\n" << pretty_print(j2) << "\n\n";
for (const auto& item : j2.array_range())
{
std::cout << "json type: " << item.type() << ", storage kind: " << item.storage() << "\n";
}
}
Output:
(1)
[
{
"id": 1,
"state": "running"
},
{
"id": 3,
"state": "running"
}
]
json type: object, storage kind: json const pointer
json type: object, storage kind: json const pointer
(2)
[
{
"id": 1,
"state": "running"
},
{
"id": 3,
"state": "running"
}
]
json type: object, storage kind: object
json type: object, storage kind: object
json j(json_array_arg, {1,2,3,4});
for (auto val : j.array_range())
{
std::cout << val << std::endl;
}
json j(json_object_arg, {
{"author", "Haruki Murakami"},
{"title", "Kafka on the Shore"},
{"price", 25.17}
});
for (const auto& member : j.object_range())
{
std::cout << member.key() << "="
<< member.value() << std::endl;
}
You can use j.as<jsoncons::string_view>()
, e.g.
json j = json::parse("\"Hello World\"");
auto sv = j.as<jsoncons::string_view>();
jsoncons::string_view
supports the member functions of std::string_view
, including data()
and size()
.
If your compiler supports std::string_view
, you can also use j.as<std::string_view>()
.
json j(json_objectarg, {
{"price", "25.17"}
});
double price = j["price"].as<double>();
If an integer exceeds the range of an int64_t
or uint64_t
, jsoncons parses it as a string
with semantic tagging bigint
.
#include <jsoncons/json.hpp>
#include <iostream>
#include <iomanip>
using jsoncons::json;
int main()
{
std::string input = "-18446744073709551617";
json j = json::parse(input);
// Access as string
std::string s = j.as<std::string>();
std::cout << "(1) " << s << "\n\n";
// Access as double
double d = j.as<double>();
std::cout << "(2) " << std::setprecision(17) << d << "\n\n";
// Access as jsoncons::bigint
jsoncons::bigint bn = j.as<jsoncons::bigint>();
std::cout << "(3) " << bn << "\n\n";
// If your compiler supports extended integral types for which std::numeric_limits is specialized
#if (defined(__GNUC__) || defined(__clang__)) && (!defined(__ALL_ANSI__) && defined(_GLIBCXX_USE_INT128))
__int128 i = j.as<__int128>();
std::cout << "(4) " << i << "\n\n";
#endif
}
Output:
(1) -18446744073709551617
(2) -1.8446744073709552e+19
(3) -18446744073709551617
(4) -18446744073709551617
Look up a key, if found, return the value converted to type T, otherwise, return a default value of type T.
json j(json_object_arg, {{"price", "25.17"}});
double price = j.get_value_or<double>("price", 25.00); // returns 25.17
double sale_price = j.get_value_or<double>("sale_price", 22.0); // returns 22.0
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
int main()
{
json j = json::parse(R"(
{
"application": "hiking",
"reputons": [
{
"rater": "HikingAsylum",
"assertion": "advanced",
"rated": "Marilyn C",
"rating": 0.90
}
]
}
)");
// Using index or `at` accessors
std::string result1 = j["reputons"][0]["rated"].as<std::string>();
std::cout << "(1) " << result1 << std::endl;
std::string result2 = j.at("reputons").at(0).at("rated").as<std::string>();
std::cout << "(2) " << result2 << std::endl;
// Using JSON Pointer
std::string result3 = jsonpointer::get(j, "/reputons/0/rated").as<std::string>();
std::cout << "(3) " << result3 << std::endl;
// Using JSONPath
json result4 = jsonpath::json_query(j, "$.reputons.0.rated");
if (result4.size() > 0)
{
std::cout << "(4) " << result4[0].as<std::string>() << std::endl;
}
json result5 = jsonpath::json_query(j, "$..0.rated");
if (result5.size() > 0)
{
std::cout << "(5) " << result5[0].as<std::string>() << std::endl;
}
}
#include <jsoncons/json.hpp>
namespace jc=jsoncons;
int main()
{
json j;
j["ByteString"] = json(byte_string_arg, std::vector<uint8_t>{ 'H','e','l','l','o' });
j["EncodedByteString"] = json("SGVsbG8=", semantic_tag::base64);
std::cout << "(1)\n";
std::cout << pretty_print(j) << "\n\n";
// Retrieve a byte string as a std::vector<uint8_t>
std::vector<uint8_t> v = j["ByteString"].as<std::vector<uint8_t>>();
// Retrieve a byte string from a text string containing base64 character values
byte_string bytes2 = j["EncodedByteString"].as<byte_string>();
std::cout << "(2) " << bytes2 << "\n\n";
// Retrieve a byte string view to access the memory that's holding the byte string
byte_string_view bsv3 = j["ByteString"].as<byte_string_view>();
std::cout << "(3) " << bsv3 << "\n\n";
// Can't retrieve a byte string view of a text string
try
{
byte_string_view bsv4 = j["EncodedByteString"].as<byte_string_view>();
}
catch (const std::exception& e)
{
std::cout << "(4) "<< e.what() << "\n\n";
}
}
Output:
(1)
{
"ByteString": "SGVsbG8",
"EncodedByteString": "SGVsbG8="
}
(2) 48 65 6c 6c 6f
(3) 48 65 6c 6c 6f
(4) Not a byte string
You can rename object members with the built in filter rename_object_key_filter
#include <sstream>
#include <jsoncons/json.hpp>
#include <jsoncons/json_filter.hpp>
using namespace jsoncons;
int main()
{
std::string s = R"({"first":1,"second":2,"fourth":3,"fifth":4})";
json_stream_encoder encoder(std::cout);
// Filters can be chained
rename_object_key_filter filter2("fifth", "fourth", encoder);
rename_object_key_filter filter1("fourth", "third", filter2);
// A filter can be passed to any function that takes a json_visitor ...
std::cout << "(1) ";
std::istringstream is(s);
json_reader reader(is, filter1);
reader.read();
std::cout << std::endl;
// or
std::cout << "(2) ";
ojson j = ojson::parse(s);
j.dump(filter1);
std::cout << std::endl;
}
Output:
(1) {"first":1,"second":2,"third":3,"fourth":4}
(2) {"first":1,"second":2,"third":3,"fourth":4}
You can use json_replace in the jsonpath
extension
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
using namespace jsoncons;
int main()
{
json j = json::parse(R"(
{ "store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}
]
}
}
)");
// Change the price of "Moby Dick" from $8.99 to $10
jsonpath::json_replace(j,"$.store.book[?(@.isbn == '0-553-21311-3')].price",10.0);
std::cout << pretty_print(booklist) << std::endl;
}
Output:
{
"store": {
"book": [
{
"author": "Nigel Rees",
"category": "reference",
"price": 8.95,
"title": "Sayings of the Century"
},
{
"author": "Evelyn Waugh",
"category": "fiction",
"price": 12.99,
"title": "Sword of Honour"
},
{
"author": "Herman Melville",
"category": "fiction",
"isbn": "0-553-21311-3",
"price": 10.0,
"title": "Moby Dick"
}
]
}
}
Suppose you have a JSON text, and need to replace one or more strings
found at a relative location path,
but are not allowed to modify anything else in the original text.
#include <jsoncons/json.hpp>
using namespace jsoncons;
class string_locator : public jsoncons::default_json_visitor
{
char* data_;
std::size_t length_;
std::vector<std::string> path_;
std::string from_;
std::vector<std::string> current_;
std::vector<std::size_t> positions_;
public:
using jsoncons::default_json_visitor::string_view_type;
string_locator(char* data, std::size_t length,
const std::vector<std::string>& path,
const std::string& from)
: data_(data), length_(length),
path_(path), from_(from)
{
}
const std::vector<std::size_t>& positions() const
{
return positions_;
}
private:
bool visit_begin_object(semantic_tag, const ser_context&, std::error_code&) override
{
current_.emplace_back();
return true;
}
bool visit_end_object(const ser_context&, std::error_code&) override
{
current_.pop_back();
return true;
}
bool visit_key(const string_view_type& key, const ser_context&, std::error_code&) override
{
current_.back() = key;
return true;
}
bool visit_string(const string_view_type& value,
jsoncons::semantic_tag,
const jsoncons::ser_context& context,
std::error_code&) override
{
if (path_.size() <= current_.size() && std::equal(path_.rbegin(), path_.rend(), current_.rbegin()))
{
if (value == from_)
{
positions_.push_back(context.position()+1); // one past quote character
}
}
return true;
}
};
void update_json_in_place(std::string& input,
const std::vector<std::string>& path,
const std::string& from,
const std::string& to)
{
string_locator locator(input.data(), input.size(), path, from);
jsoncons::json_reader reader(jsoncons::string_view(input), locator);
reader.read();
for (auto it = locator.positions().rbegin(); it != locator.positions().rend(); ++it)
{
input.replace(*it, from.size(), to);
}
}
int main()
{
std::string input = R"(
{
"Cola" : {"Type":"Drink", "Price": 10.99},"Water" : {"Type":"Drink"}, "Extra" : {"Cola" : {"Type":"Drink", "Price": 8.99}}
}
)";
try
{
std::cout << "(original)\n" << input << "\n";
update_json_in_place(input, {"Cola", "Type"}, "Drink", "SoftDrink");
std::cout << "(updated)\n" << input << "\n";
}
catch (std::exception& e)
{
std::cout << e.what() << "\n";
}
}
Output:
(original)
{
"Cola" : {"Type":"Drink", "Price": 10.99},"Water" : {"Type":"Drink"}, "Extra" : {"Cola" : {"Type":"Drink", "Price": 8.99}}
}
(updated)
{
"Cola" : {"Type":"SoftDrink", "Price": 10.99},"Water" : {"Type":"Drink"}, "Extra" : {"Cola" : {"Type":"SoftDrink", "Price": 8.99}}
}
#include <iostream>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
// for brevity
using jsoncons::json;
namespace jsonpointer = jsoncons::jsonpointer;
int main()
{
json input = json::parse(R"(
{
"discards": {
"1000": "Record does not exist",
"1004": "Queue limit exceeded",
"1010": "Discarding timed-out partial msg"
},
"warnings": {
"0": "Phone number missing country code",
"1": "State code missing",
"2": "Zip code missing"
}
}
)");
json flattened = jsonpointer::flatten(input);
std::cout << "(1)\n" << pretty_print(flattened) << "\n";
json unflattened1 = jsonpointer::unflatten(flattened);
std::cout << "(2)\n" << pretty_print(unflattened1) << "\n";
json unflattened2 = jsonpointer::unflatten(flattened,
jsonpointer::unflatten_options::assume_object);
std::cout << "(3)\n" << pretty_print(unflattened2) << "\n";
}
Output:
(1)
{
"/discards/1000": "Record does not exist",
"/discards/1004": "Queue limit exceeded",
"/discards/1010": "Discarding timed-out partial msg",
"/warnings/0": "Phone number missing country code",
"/warnings/1": "State code missing",
"/warnings/2": "Zip code missing"
}
(2)
{
"discards": {
"1000": "Record does not exist",
"1004": "Queue limit exceeded",
"1010": "Discarding timed-out partial msg"
},
"warnings": ["Phone number missing country code", "State code missing", "Zip code missing"]
}
(3)
{
"discards": {
"1000": "Record does not exist",
"1004": "Queue limit exceeded",
"1010": "Discarding timed-out partial msg"
},
"warnings": {
"0": "Phone number missing country code",
"1": "State code missing",
"2": "Zip code missing"
}
}
Note that unflattening a json object of JSON Pointer-value pairs has no unique solution. An integer appearing in a path could be an array index or it could be an object key.
#include <iostream>
#include <cassert>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpath/jsonpath.hpp>
// for brevity
using jsoncons::json;
namespace jsonpath = jsoncons::jsonpath;
int main()
{
json input = json::parse(R"(
{
"application": "hiking",
"reputons": [
{
"rater": "HikingAsylum",
"assertion": "advanced",
"rated": "Marilyn C",
"rating": 0.90
},
{
"rater": "HikingAsylum",
"assertion": "intermediate",
"rated": "Hongmin",
"rating": 0.75
}
]
}
)");
json result = jsonpath::flatten(input);
std::cout << pretty_print(result) << "\n";
json original = jsonpath::unflatten(result);
assert(original == input);
}
Output:
{
"$['application']": "hiking",
"$['reputons'][0]['assertion']": "advanced",
"$['reputons'][0]['rated']": "Marilyn C",
"$['reputons'][0]['rater']": "HikingAsylum",
"$['reputons'][0]['rating']": 0.9,
"$['reputons'][1]['assertion']": "intermediate",
"$['reputons'][1]['rated']": "Hongmin",
"$['reputons'][1]['rater']": "HikingAsylum",
"$['reputons'][1]['rating']": 0.75
}