Trivial integration

  • single header file
  • no build system needed
  • vanilla C++11
  • works with GCC 4.8+, Clang 3.4+ and MSVC 2015+
#include <nlohmann/json.hpp>

// for convenience
using json = nlohmann::json;

int main()
{
    // a JSON pretty-printer in 3 lines
    json j;
    std::cin >> j;
    std::cout << std::setw(4) << j << std::endl;
}

$ g++ pretty.cpp -std=c++11 -o pretty

First-class datatype

  • write JSON like any other C++ literal
  • initializer lists become arrays and objects
json j = {
  {"pi", 3.141},
  {"happy", true},
  {"name", "Niels"},
  {"nothing", nullptr},
  {"answer", {
    {"everything", 42}
  }},
  {"list", {1, 0, 2}},
  {"object", {
    {"currency", "USD"},
    {"value", 42.99}
  }}
};

Array construction

  • arrays work the way you expect
  • adding to a null value makes it an array
// create null value
json j;

// add via member function
j.push_back("Hello");
j.emplace_back("World");

// add via += operator
j += false;

// add via index
j[2] = 42.3;

// gaps are filled with null
j[4] = "World";

Object construction

  • objects work the way you expect
  • adding to a null value makes it an object
// create null value
json j;

// add via member function
j.emplace("key2", "value");
j.push_back({"key", "value"});

// add via += operator
j += {"another key", false};

// add via key
j["pi"] = 3.1415926;
j["answer"]["everything"] = 42;

Container operations

  • use JSON like an STL container
  • satisfies the ReversibleContainer requirements
// container empty?
j.empty();

// current number of elements
j.size();

// maximal number of elements
j.max_size();

// swap elements
j.swap(j2);
std::swap(j, j2);

// clear a container
j.clear();

STL containers

  • switch between STL and JSON containers
  • conversion works in both directions
// some STL containers
std::vector<int> v;
std::unordered_map<std::string, bool> m;

// automatic transformation
json j_array(v);
json j_object(m);

// works both ways
auto l = j_array.get<std::list<int>>();
auto u = j_object
    .get<std::map<std::string, bool>>();

Type conversion

  • convert your own types to and from JSON
  • one macro writes to_json and from_json
  • or write both functions for full control
// some user-defined type
struct person {
    std::string name;
    int age;
};

// defines to_json and from_json
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person,
                                   name, age)

// conversion in action
person p {"Ned Flanders", 60};
json j = p;
// {"age": 60, "name": "Ned Flanders"}

auto p2 = j.get<person>();

Type deduction

  • types are deduced automatically
  • member functions tell you the type
// type deduction
json j1 = nullptr;
json j2 = false;
json j3 = 12.23;
json j4 = {1, 2, 3};
json j5 = {{"key", "value"}};
json j6 = "Hello";

// quick checks
j1.is_null();
j2.is_boolean();
j3.is_number();
j4.is_array();
j5.is_object();
j6.is_string();

Serialization

  • to a string or a stream
  • pretty-printing included
// default serialization
auto s1 = j.dump();

// pretty-printed
int indent = 4;
std::string s2 = j.dump(indent);

// to stream
std::cout << j << std::endl;

// pretty-printed
std::cout << std::setw(indent) << j << std::endl;

Deserialization

  • parse strings, streams and files
  • the _json literal puts plain JSON in your code
  • comments and trailing commas on request
// from string
json j1 = json::parse("[1,null,false]");

// from file
std::ifstream f("example.json");
json j2 = json::parse(f);

// from string literal
json j3 = "[3.14159]"_json;

// from raw string literal
json j4 = R"(
  {"one": 1, "two": 2, "three": [1, 2, 3]}
)"_json;

SAX parsing

  • the parser can emit SAX events
  • process input without building a JSON value
// define a SAX event processor
struct sax_processor : json::json_sax_t
{
    bool boolean(bool val) override
    {
        return true;
    }

    // implement other functions...
};

// create object
sax_processor my_processor;

// use it to process input
json::sax_parse(input, &my_processor);

Zero-copy views {badge: NEW IN 3.13}

  • parse without building a tree: 16 bytes per value
  • strings and numbers stay in the source text
  • materialize a json value only where you need one
#include <nlohmann/json_view.hpp>

// parse into a flat index, not a tree
std::ifstream f("big.json");
auto doc = nlohmann::json_document::parse(f);

// read-only views with familiar access
auto view = doc.root();
double pi = view["pi"].get<double>();
for (auto v : view["list"])
    std::cout << v.get<int>() << '\n';

// build a json value only where needed
json part = view["list"].materialize();

JSON Pointer

  • address any element with a JSON Pointer
  • flatten a value to pointer/value pairs, and back
json j = {"good", "bad", "ugly"};

// JSON pointer to the 3rd list element
auto ptr = json::json_pointer("/2");

// use JSON pointer to access element
j[ptr] = {{"it", "cattivo"}};

// user-defined string literal
j["/2/en"_json_pointer] = "ugly";
// ["good","bad",{"en":"ugly","it":"cattivo"}]

json flat = j.flatten();
// {"/0":"good","/1":"bad","/2/en":"ugly",
//  "/2/it":"cattivo"}

json j2 = flat.unflatten();  // j == j2

JSON Patches

  • use JSON Patch to describe edit operations
  • create diffs from two JSON values
  • or use a JSON Merge Patch that looks like the document
json source = R"({"F": "Paris", "D": "Berlin"})"_json;

// a JSON Patch (RFC 6902)
json p1 = R"([
  {"op": "add", "path": "/GB", "value": "London"}
])"_json;
json target = source.patch(p1);

// create a diff from two JSON values
json p2 = json::diff(target, source);
// [{"op": "remove", "path": "/GB"}]

// a JSON Merge Patch (RFC 7396)
target.merge_patch(R"({"F": null})"_json);
// {"D": "Berlin", "GB": "London"}

Binary formats

  • compact binary representations
  • BJData, BON8, BSON, CBOR, MessagePack and UBJSON
// a JSON value
json j = R"({"compact": true, "schema": 0})"_json;

// serialize to CBOR
std::vector<std::uint8_t> v = json::to_cbor(j);
// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61,
// 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68,
// 0x65, 0x6D, 0x61, 0x00

// roundtrip
json j_from_cbor = json::from_cbor(v);

// the same for the other formats
auto b = json::to_bon8(j);
auto m = json::to_msgpack(j);

Algorithms

  • the standard algorithms just work
  • sort, find, count, merge and more
// sort JSON containers
std::sort(j.begin(), j.end());

// find element
auto pos = std::find(j.begin(), j.end(),
                     json("needle"));

// count all numbers
auto n = std::count_if(j.begin(), j.end(),
    [](const json& v) { return v.is_number(); });

// merge two sorted containers
std::merge(j1.begin(), j1.end(),
           j2.begin(), j2.end(),
           std::back_inserter(j3));

Iterators

  • iterators, const iterators, and reverse iterators
  • random access for arrays
  • items() gives you keys and values
json j = {1, 2, 3, 4, 5};

// range for
for (auto& el : j)
{
    // print all values
    std::cerr << el << '\n';
}

// classical iterators
for (auto it = j.begin(); it != j.end(); ++it)
{
    // double all values
    *it = it->get<int>() * 2;
}

Structured bindings

  • since C++17, unpack keys and values with structured bindings
// JSON object
auto j = R"({"foo": 23, "bar": 42})"_json;

// structured bindings
for (auto& [key, val] : j.items())
{
    std::cerr << key << ':' << val << '\n';
}