jsoncons: An Open Source C++ Library for JSON Processing

Let’s talk about the jsoncons C++ library, a treasure trove for developers, and write an easy-to-understand article with code examples.

A New Choice for JSON Processing: Making JSON Operations in C++ as Easy as Breathing with jsoncons

In modern C++ development, JSON (JavaScript Object Notation) is ubiquitous. Whether for configuration files, network API communication, or data persistence, JSON is the preferred lightweight data exchange format.

However, native C++ does not have built-in support for JSON. Developers often need to look for third-party libraries to handle JSON. Well-known libraries such as nlohmann/json and RapidJSON are available, but today we will introduce another powerful, efficient, and elegantly designed option: jsoncons.

What is jsoncons?

jsoncons is an open-source C++ library designed for processing JSON (as well as binary formats like BSON, CBOR, MessagePack, and UBJSON). Its name comes from “JSON Cons” (JSON construction), and its core philosophy is to provide a type-safe, easy-to-use, high-performance interface for parsing, generating, and manipulating JSON data.

Why choose jsoncons?

  1. Single Header File Library: In most cases, you only need to include one header file jsoncons/json.hpp, without complex compilation and linking processes, making integration extremely simple.
  2. STL Style Interface: It heavily utilizes patterns from the C++ Standard Template Library (STL), such as iterators and container-style access, making it very easy to learn for C++ developers familiar with STL.
  3. Type Safety: It provides a jsoncons::json type that can be manipulated like std::map or std::vector, allowing many errors to be caught at compile time.
  4. High Performance: Designed with performance in mind, it offers fast parsing and serialization speeds.
  5. Rich Features: Supports advanced features such as JSON Schema validation, JSON Patch, JSON Pointer, and streaming parsing/generation.
  6. Cross-Platform: Supports C++11 and above, usable on various platforms and compilers.

Quick Start: Installation and Inclusion

jsoncons is a header-only library, making installation very simple:

  1. Download: Visit its GitHub repository (https://github.com/danielaparker/jsoncons) to download the source code.
  2. Include: Add the downloaded include directory to your compiler’s include path.
  3. Include the header file in your code:
    #include <jsoncons/json.hpp>
    // Use namespace to simplify code (optional)
    using namespace jsoncons;

Practical Exercise: Code Examples

Let’s explore the charm of jsoncons through several common scenarios.

Scenario 1: Creating and Modifying JSON

Imagine we want to build a JSON object representing user information.

#include <iostream>
#include <jsoncons/json.hpp>

using namespace jsoncons;

int main() {
    // Create an empty JSON object
    json user;

    // Add key-value pairs like a map
    user["name"] = "Zhang San";
    user["age"] = 30;
    user["email"] = "[email protected]";
    user["is_active"] = true;

    // Create a JSON array
    json hobbies;
    hobbies.push_back("Reading");
    hobbies.push_back("Swimming");
    hobbies.push_back("Programming");

    // Set the array as a property of the object
    user["hobbies"] = hobbies;

    // Output JSON string (default formatting)
    std::cout << "(1) Constructed JSON:\n" << pretty_print(user) << "\n\n";

    return 0;
}

Output:

(1) Constructed JSON:
{
    "age": 30,
    "email": "[email protected]",
    "hobbies": ["Reading", "Swimming", "Programming"],
    "is_active": true,
    "name": "Zhang San"
}

See, it’s very similar to manipulating std::map<std::string, std::any> or std::unordered_map!

Scenario 2: Parsing a JSON String

Now, we have a JSON string that we need to parse and read data from.

#include <iostream>
#include <jsoncons/json.hpp>

using namespace jsoncons;

int main() {
    // JSON string to be parsed
    std::string json_str = R"(
    {
        "product": "Laptop",
        "price": 5999.99,
        "in_stock": true,
        "tags": ["Electronics", "Office", "High Performance"],
        "dimensions": {
            "width": 35.6,
            "height": 2.1,
            "depth": 24.3
        }
    }
    )";

    try {
        // Parse JSON string
        json product = json::parse(json_str);

        // Read basic types
        std::string name = product["product"].as<std::string>();
        double price = product["price"].as<double>();
        bool inStock = product["in_stock"].as<bool>();

        std::cout << "(2) Product Information:\n";
        std::cout << "  Name: " << name << "\n";
        std::cout << "  Price: ¥" << price << "\n";
        std::cout << "  In Stock: " << (inStock ? "Yes" : "No") << "\n\n";

        // Iterate over array
        std::cout << "  Tags: ";
        for (const auto& tag : product["tags"].array_range()) {
            std::cout << tag.as<std::string>() << " ";
        }
        std::cout << "\n\n";

        // Access nested object
        const json& dims = product["dimensions"];
        std::cout << "  Dimensions (cm): Width " << dims["width"].as<double>()
                  << ", Height " << dims["height"].as<double>()
                  << ", Depth " << dims["depth"].as<double>() << "\n";

    } catch (const std::exception& e) {
        std::cerr << "Error parsing JSON: " << e.what() << std::endl;
    }

    return 0;
}

Output:

(2) Product Information:
  Name: Laptop
  Price: ¥5999.99
  In Stock: Yes
  Tags: Electronics Office High Performance 

  Dimensions (cm): Width 35.6, Height 2.1, Depth 24.3

Note the as<T>() method, which provides type-safe conversion. If the types do not match, it will throw an exception.

Scenario 3: Reading and Writing JSON from Files

In practical applications, JSON data is often stored in files.

#include <iostream>
#include <fstream>
#include <jsoncons/json.hpp>

using namespace jsoncons;

int main() {
    // Read JSON from file (assuming the filename is data.json)
    std::ifstream input_file("data.json");
    if (!input_file) {
        std::cerr << "Cannot open file data.json\n";
        return 1;
    }

    json config;
    try {
        // Parse from file stream
        input_file >> config;
        input_file.close();

        std::cout << "(3) Configuration read from file:\n";
        std::cout << pretty_print(config) << "\n\n";

        // Modify configuration
        config["database"]["port"] = 5432;
        config["logging"]["level"] = "DEBUG";

        // Write back to file
        std::ofstream output_file("updated_config.json");
        if (!output_file) {
            std::cerr << "Cannot create file updated_config.json\n";
            return 1;
        }
        // Use pretty_print for formatted output
        output_file << pretty_print(config);
        output_file.close();

        std::cout << "Configuration updated and saved to updated_config.json\n";

    } catch (const std::exception& e) {
        std::cerr << "Error processing JSON file: " << e.what() << std::endl;
    }

    return 0;
}

Scenario 4: Using JSON Pointer and Patch (Advanced Features)

jsoncons supports JSON Pointer (like XPath for XML) to locate specific nodes in JSON, as well as JSON Patch to describe changes to JSON documents.

#include <iostream>
#include <jsoncons/json.hpp>
#include <jsoncons_ext/jsonpointer/jsonpointer.hpp>
#include <jsoncons_ext/jsonpatch/jsonpatch.hpp>

using namespace jsoncons;
using namespace jsoncons::jsonpointer;
using namespace jsoncons::jsonpatch;

int main() {
    json doc = json::parse(R"(
    {
        "store": {
            "book": [
                {"title": "First Book", "price": 8.95},
                {"title": "Second Book", "price": 12.50}
            ],
            "bicycle": {"color": "Red", "price": 19.95}
        }
    })");

    // Use JSON Pointer to get nested value
    json* price_ptr = jsonpointer::get(doc, "/store/book/0/price");
    if (price_ptr) {
        std::cout << "(4) Price of the first book: " << price_ptr->as<double>() << "\n";
    }

    // Create a JSON Patch to modify the document
    json patch = json::parse(R"([
        {"op": "replace", "path": "/store/book/0/title", "value": "Updated First Book"},
        {"op": "add", "path": "/store/book/-", "value": {"title": "Third Book", "price": 15.00}}
    ])");

    // Apply Patch
    jsonpatch::apply_patch(doc, patch);

    std::cout << "Document after applying Patch:\n" << pretty_print(doc) << "\n";

    return 0;
}

Conclusion

jsoncons is a powerful and well-designed C++ JSON library. It greatly simplifies JSON processing in C++ by providing an intuitive, STL-like interface. Whether for simple configuration reading and writing or complex API data exchanges, jsoncons can handle it all.

Review of Advantages:

  • Easy to Use: Header-only library, just include it.
  • Type Safety: The as<T>() method avoids runtime type errors.
  • Excellent Performance: Fast parsing and serialization speeds.
  • Comprehensive Features: Supports various data formats and advanced features.

If you are looking for a reliable and efficient C++ JSON library, jsoncons is definitely worth trying! Check out its GitHub repository for more detailed documentation and rich examples.

Leave a Comment