JSON (JavaScript Object Notation) has become the de facto standard for data exchange on the web. With its simple syntax, JSON provides an easy way to represent structured data that can be parsed in most programming languages.

As a C++ developer, you‘ll likely encounter JSON data from APIs, configuration files, or other interfaces. Being able to efficiently parse JSON in C++ unlocks the ability to build powerful applications that leverage web services and data.

In this comprehensive guide, you‘ll learn:

  • What is JSON and its key syntax rules
  • JSON data types like objects and arrays
  • Available C++ JSON parsing libraries
  • Hands-on code examples for parsing JSON
  • Serializing C++ objects to/from JSON
  • Best practices for working with JSON in C++

After reading, you‘ll have the confidence to parse even complex JSON data structures in your C++ projects. Let‘s get started!

What is JSON?

JSON stands for JavaScript Object Notation. It emerged in the early 2000s as a lightweight alternative to XML for transmitting data over the web.

The core syntax rules of JSON are:

  • Data is represented as key/value pairs – This provides an intuitive, dictionary-style lookup for access.
  • Curly braces hold objects – This allows nesting different JSON objects.
  • Square brackets hold arrays – Useful for structuring related data.
  • Comma-separated syntax – Allows both arrays and nested object definitions.

Overall, these simple conventions allow JSON to be highly expressive in modeling real-world data, while avoiding verbosity.

Unlike binary representations like protocol buffers, JSON is human-readable text. This makes it easy to inspect and debug. The ubiquity of JSON across modern APIs makes it an essential format for any C++ developer.

JSON Data Types

JSON structures data in two primary ways – with objects and arrays.

JSON Objects

A JSON object is an unordered collection of key/value pairs enclosed in curly braces {}. It is akin to maps, dictionaries, hashes, and associated arrays in other languages.

For example:

{
  "name": "John Doe",
  "age": 35,
  "is_admin": false
}

Here the keys are name, age, and is_admin. The values are the corresponding data.

JSON objects provide a flexible way to model real-world entities like users, products, files, etc in a key-value store.

Nesting is supported to allow rich data structures:

{
  "name": "John Doe",
  "contact": {
    "phone": "(408)-555-1234", 
    "email": "john@xyz.com"  
  },
  "scores": [90, 75, 85]
}

Here the contact and scores keys refer to nested JSON objects for additional structure.

JSON Arrays

The JSON array contains an ordered collection of values enclosed in square brackets [].

For example:

[
  "John",
  "Jill",
  "Jim" 
]

JSON arrays are useful for storing related sequential data. Their index allows orderly accessing and manipulation of values.

Arrays can be composed into other JSON objects:

{
  "name": "John Doe",
  "phone_numbers": [
    "(408)-555-1234",
    "(650)-555-1234"
  ]
}

Here the phone_numbers value is an array letting us store multiple phone numbers cleanly.

Together JSON objects and arrays are able to represent rich, complex data. Their versatility explains why JSON has been universally adopted.

Why Parse JSON in C++

For modern C++ applications, parsing JSON provides 3 major benefits:

1. Handle Responses from Web APIs

Nearly all web APIs today exchange data in a JSON format. Working with REST APIs entails sending JSON requests and handling JSON responses. This necessitates efficiently parsing JSON in C++ apps.

For example, the Hacker News API returns JSON search results:

{
   "hits":[
      {
         "title":"Ask HN: Does working at FAANG make you a better engineer?",
         "url":"https://news.ycombinator.com/item?id=17155264",
         "points":138,
         "author":"throwaway2016a",
         "num_comments":68,
         "created_at":"2018-03-01T19:30:17.000Z", 
      }
   ]
}

Being able to directly work with such JSON responses allows rapid API integration into C++ services.

2. Load Configuration Data

JSON is a popular format for external configuration files. All major cloud providers like AWS, GCP, and Azure use JSON configuration files.

For instance, Kubernetes pods are configured declaratively through a json file:

{
  "apiVersion": "v1",
  "kind": "Pod",
  "metadata": {
    "name": "example-pod"
  },
  "spec": { 
    "containers": [
      {
        "name": "nginx",
        "image": "nginx:latest"  
      }
    ]
  }
} 

By parsing such JSON configs in C++, complex applications can leverage external declarative administration of resources and options.

3. Serialize Custom C++ Objects

While JSON started in JavaScript realm, it has evolved into language-agnostic structured data standard. Its simplicity allows serializing C++ classes into portable JSON representations.

For example, a custom User class can be serialized into JSON as:

User u;
u.name = "John"; 
u.id = 10;

// Serialize into JSON
string json = JSON.stringify(u); 

// Prints {"name":"John", "id":10}

This allows transmitting C++ objects over APIs and networks for distributed computing use cases.

JSON Parsers for C++

Unlike other languages like JavaScript and Python, C++ does not have inherent support for parsing JSON text. We need to leverage 3rd-party open-source JSON libraries.

Let‘s explore the top JSON parsers for C++:

1. nlohmann/json

nlohmann/json is a popular header-only JSON parser for C++. It provides:

  • Header-only simplicity with no external dependencies
  • Abstraction for JSON values with json class
  • Fast parsing and serialization capabilities
  • Integration with other types through adapters
  • STL-style access and manipulation of JSON values

For most common use cases of parsing/generating JSON, nlohmann/json offers the best balance across speed, flexibility and ease-of-use.

2. RapidJSON

RapidJSON is an ultra fast JSON parser tuned for C++ efficiency focusing on:

  • Blazing performance rivaling handwritten parsers
  • Lowest memory utilization through in-situ parsing
  • Header-only design without external dependencies
  • Optional integration with Streams and Custom Allocators

For applications where every CPU cycle or MB of memory matters, RapidJSON provides the best-in-class optimization.

3. Boost.JSON

Boost is the renowned peer-reviewed C++ library ecosystem. It contains Boost.JSON component for JSON manipulation.

Key aspects:

  • Robust and portable support across many platforms
  • Compliant with RFC8259 JSON specification
  • Flexibility through parametric polymorphism techniques
  • Integration with other Boost libraries

For legacy applications leveraging Boost already, Boost.JSON integrates cleanly keeping dependency uniform.

Making a Choice

As we can see, all 3 libraries take different approaches.

nlohmann/json focuses on simplicity and elegance of API. RapidJSON optimizes for speed and memory efficiency. Boost.JSON provides portability leveraging Boost ecosystem.

For most use cases, I recommend starting with nlohmann/json for its approachability. As needs arise, switching to RapidJSON or Boost.JSON is straightforward since all support similar data access patterns.

Now let us look at practical examples!

Hands-on: Parsing JSON in C++

Let‘s go through example code for common JSON parsing tasks:

  • Accessing values
  • Nested object traversal
  • Nested array traversal
  • Error handling

For illustration, we will use the following sample JSON document with nested object and array:

{
   "name":"John",
   "age":35,
   "is_married":false,
   "address": {
      "street":"5th Ave",
      "city":"New York",
      "state":"NY
   },
   "previous_jobs":[
      "Software Engineer",
      "Manager"
   ]
}

And assume it is stored in the json_doc variable. The examples use C++17 features and nlohmann/json library.

Accessing JSON Values

Let‘s start by directly accessing values at the root level of our JSON document:

#include <iostream>
#include "json.hpp"

using json = nlohmann::json; 

int main() {

  json json_doc = R"(
   {
      "name":"John",  
      "age":35,
      "is_married":false
   }
  )"_json;

  // Access values  
  std::string name = json_doc["name"]; 
  int age = json_doc["age"];
  bool is_married = json_doc["is_married"];

  // Print  
  std::cout << name << " " << age << " " << is_married; 

  // Prints "John 35 false"
}

Here we simply use subscript syntax to access the keys just like a map. The convenience method .get<T>() can also be used.

Values are automatically converted to desired C++ types like string, int, bool etc.

Nested Object Traversal

Now let‘s access nested objects nested at arbitrary depth:

#include <iostream>
#include "json.hpp"

int main() {

  json json_doc = R"(
    {
      "address": {
        "street":"5th Ave"  
      }
    }
  )"_json;

  // Traverse nested object
  std::string street = json_doc["address"]["street"];

  // Print
  std::cout << street;

  // Prints "5th Ave"
}

This allows directly drilling down JSON sub-objects through successive [] access. The syntax matches the mental model of navigating a nested structure.

Nested Array Traversal

Similar to nested objects, we use [] to access JSON arrays:

#include <iostream>
#include "json.hpp"

int main() {

  json json_doc = R"(
    {
     "previous_jobs":[ 
        "Software Engineer",
        "Manager"
      ]
    }
  )"_json;

  // Get array
  json previous_jobs = json_doc["previous_jobs"];  

  // Access element  
  std::string job_0 = previous_jobs[0]; 

  // Print
  std::cout << job_0;

  // Prints "Software Engineer"
}

The array access simplicity allows easy manipulation in iterative algorithms.

Handling Missing Keys

A common scenario is handling missing keys or null values. This can be done through .count() and .empty():

#include <iostream>
#include "json.hpp"

using json = nlohmann::json;

int main() {

  json json_doc = R"(
    {
    }
  )"_json;

  // Check if key exists
  if(json_doc.count("address") == 0) {
    std::cout << "Key does not exist\n";  
  }

  // Check if value at key is null  
  if(json_doc["phone"].empty()) {
    std::cout << "Value is null\n";
  }
}

These kinds of robust null checks allow writing resilient JSON parsing logic in C++.

As we can see, nlohmann/json makes it quite simple to handle varied JSON parsing scenarios. With basic working knowledge, you will be prepared to handle even complex documents.

Now that we know core parsing techniques, let us look at C++ model mapping.

Mapping JSON to C++ Models

While basic JSON navigation is simple enough, we often desire mapping parsed JSON into C++ models. There are two approaches:

1. Deserialize into Classes

We can declare corresponding C++ classes and deserialize JSON straight into instances.

For example, for above User JSON document:

// User class 
struct User {
  std::string name;
  int age;
  bool is_married;

  // Omitted constructors  
};

// Parse and map to object
json json_doc = R"(
  {
    "name":"John",
    "age":35,  
    "is_married":false
  }  
)"_json;

User user = json_doc.get<User>(); 

Here json.get<T>() handles mapping between JSON structure and C++ class fields automatically.

Similarly complex nested JSON arrays and objects can be deserialized into C++ types with relationships modeled as references or identifiers.

2. Read into C++ Data Structures

For flexibility, we can read JSON using simple data structures like maps, vectors, strings without needing formal classes:

// Store parsed json in map  
std::map<std::string, json> json_doc{
  {"name", "John"},
  {"age",  35},
  {"is_married", false}
};

// Access as standard map
std::string name = json_doc["name"];

This builds abstraction over JSON providing ease of use over raw string manipulation.

Based on the use case, both class binding and flexible data structures offer simple integration of JSON with C++ programs.

Serializing C++ Objects to JSON

So far, we focused on parsing JSON into C++ models. The data flow can be reversed – serialize C++ objects into JSON.

This is simply done through json.dump() method:

// User class
struct User {
  std::string name;
  int age; 
};

User user; 
user.name = "John";
user.age = 35;

// Serialize into JSON string
json json_user = json.dump(user);  

// Prints {"name":"John", "age":35}
std::cout << std::setw(4) << json_user;

For custom classes, defining to_json and from_json methods allow two-way conversion between JSON and C++ models.

The ability to serialize C++ data structures into JSON string opens avenues for transmitting objects over network through open protocols.

Best Practices for JSON in C++

While JSON handling in C++ is quite accessible, let us look at some tips for working effectively:

1. Validate Early

In production apps, ensure to validate structure, data types and values of incoming JSON data. Catching issues early prevents unexpected errors later.

2. Handle Invalid JSON

Prepare for gracefully handling poorly formed JSON by wrapping parsing in try/catch blocks and returning error notifications.

3. Use Strict Types

Declare external model interfaces with strict types like std::string and ints rather than plain json objects. This reduces bugs through compile-time checking.

4. Validate Deserialization

After deserializing JSON into a C++ class, check validity by accessing couple fields to catch any mapping issues early.

5. Use Enumerations

For fields with a limited defined set of values, use C++ enums over plain strings in external JSON. This adds type safety.

6. Prepare for Evolution

Expect structure of incoming JSON to evolve by wrapping handling of deprecated fields in version checks, allowing backwards compatibility.

Adopting these patterns will ensure your JSON interfaces are robust and resilient for mission critical systems.

Conclusion

JSON has become the ubiquitous data format across modern web infrastructure. As C++ developers, being able to efficiently parse JSON unlocks integration opportunities with powerful APIs and services.

We learned:

  • JSON represents data as objects and arrays in human-readable form
  • Top JSON parsers for C++ like nlohmann/json, RapidJSON and Boost.JSON
  • Accessing and manipulating JSON directly maps to standard C++ types
  • Serializing C++ objects into JSON enables complex data transmission
  • Best practices like input validation and enum usage

With this comprehensive guide to JSON under your belt, go forth and build more versatile C++ applications leveraging the world of web APIs!

Similar Posts