Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Accessing JSON Data with C++

3.71/5 (9 votes)
27 May 2016CPOL8 min read 168.3K   2.8K  
This article explains how to read, process, and write JSON data using the JsonCpp toolset.

Introduction

In modern web development, JSON (JavaScript Object Notation) has surpassed XML (Extensible Markup Language) as the most popular format for structured data. Web applications usually rely on JavaScript to process JSON data, but desktop applications may also need to read and write JSON data. In these cases, it's helpful to know how to access JSON-formatted data using C++.

It isn't difficult to code a JSON parser from scratch, but a handful of solutions have already been built for the purpose. One site compares three toolsets: JsonCpp, Casablanca, and JSON Spirit. This article focuses exclusively on JsonCpp.

To be precise, this article explains how to use JsonCpp to parse JSON data, process the data, and write the data to a string or to an output stream. But first, it's important to understand how to obtain and build the toolset.

1. Obtaining and Building JsonCpp

Baptiste Lepilleur has released JsonCpp as public domain, but before you can use it, you need to download and build the source code. This code is available on GitHub, and you can clone the repository using Git or download the archive by clicking the green Clone or Download button.

To build JsonCpp, you need to have the CMake build system available. Then you can build the library by following four steps:

  1.  Change to the directory containing the JsonCpp source code and create a directory to hold the build files: mkdir -p build/debug
  2.  Change to the new directory: cd build/debug
  3.  Run CMake: cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON \
    -DBUILD_SHARED_LIBS=OFF -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../..
  4.  Build the toolset: make

If this process completes successfully, the build/debug folder will contain a static library, libjsoncpp.a, in its src/lib_json folder. In addition, the top-level JsonCpp folder contains a directory named include that contains header files such as json/json.h and json/writer.h. These are needed to compile C++ applications that access JsonCpp routines.

The build can be configured by setting arguments of the cmake command. For example, CMAKE_BUILD_TYPE can be set to None, Debug, Release, RelWithDebInfo, MinSizeRel, or Coverage. Also, if BUILD_SHARED_LIBS is set to ON, the build will produce a shared library in the build/debug/src/lib_json folder. The example code in this article assumes that a static library, libjsoncpp.a, is available.

2. Overview

The JsonCpp toolset consists of classes that belong to the Json namespace. This article focuses on five of them, and Table 1 lists their names and the purposes they serve:

Table 1: Important Classes of the JsonCpp Package
Class Purpose
Json::Reader Read and parse JSON data
Json::Value Store JSON data and make it accessible
Json::FastWriter Write JSON data to a single-line string
Json::StyledWriter Write JSON data in human-readable form
Json::StyledStreamWriter  Write JSON data to an output stream

This article discusses these classes in the given order. The next section looks at parsing JSON data with a Json::Reader. The following section examines how to process JSON data with a Json::Value. The section following that explains how to write JSON data using the Json::FastWriter, Json::StyledWriter, and Json::StyledStreamWriter classes.

3. Parsing JSON Data

The central function in the Json::Reader class is parse. There are three overloaded functions:

  • parse(const std::string& document, Json::Value& root, bool collectComments = true)
  • parse(const char* start, const char* end, Json::Value& root, 
        bool collectComments = true)
  • parse(std_istream& is, Json::Value& root, bool collectComments = true)

The goal of parse is to convert text into a Json::Value, which is a C++ representation of a JSON object. The difference between the three functions involves the source of the text. The first function reads text from a string, the second reads character data in a memory range, and the third reads text from an input stream. 

These functions all return a bool that identifies whether the text was successfully parsed. If the parse was successful, the root argument will contain valid JSON data. If the last argument is set to true, the JSON data will include comments.

In addition to parse, the Json::Reader class provides functions that process parsing errors. getFormattedErrorMessages returns a string that identifies errors detected during the parsing process. The following code demonstrates how a Json::Reader can be used for parsing and error detection.

Json::Reader reader;
Json::Value root;  
std::string text = "{ \"first\": 1; \"second\": 2}";

if(!reader.parse(text, root)) {
  std::cout << reader.getFormattedErrorMessages() << std::endl;
}

In this case, parse returns false because the two name/value pairs are separated by a semicolon instead of a comma. As a result, getFormattedErrorMessages returns the following:

* Line 1, Column 13
  Missing ',' or '}' in object declaration

Unlike JavaScript, JsonCpp expects property names (first, second) to be surrounded in double-quotes. If a property name isn't contained in quotes, parse will return false.

4. Working with JSON Data

If parse completes successfully, the JSON data can be accessed through the Json::Value object. This makes it possible to access JSON properties using C++ map notation. For example, in the preceding code, a property's name was first and its value was 1. If root is the name of the Json::Value, root["first"] will return 1. Similarly, root["second"] will return 2. Note that the value returned by the map is of type Json::Value. In other words, a Json::Value can be thought of as a map of other Json::Values.

A value's type can be obtained by calling type(). This returns a value of the ValueType enumerated type, which may be nullValue, intValue, uintValue, realValue, stringValue, booleanValue, arrayValue, or objectValue. For example, root["second"].type() returns Json::uintValue

Table 2 lists type and nine other functions of the Json::Value class.

Table 2: Functions of Json::Value
Function Description
type() Identifies the type of the value's data
size() Provides the number of values contained in the value
empty() Returns true if the value doesn't contain any values
clear() Removes all values from the value
resize(ArrayIndex size) Resizes the value
append(Json::Value val) Appends the value to the end of the value
get(ArrayIndex index,
      Json::Value default)
Returns the value at the given index or the default
isMember(std::string &key) Identifies if the value contains a value with the given key
removeMember(std::string &key)  Removes the value with the given key
toStyledString() Returns a string containing the value's value

These functions are easy to use and understand so long as you think of a Json::Value as a map of named Json::Values. For example, suppose that a Json::Value named root is created from the following JSON object: { "num": 1, "obj": { "str": "Hi" }}.

  • root.size() returns 2 because the object contains two values
  • root["num"].type() returns 1, which corresponds to Json::uintValue
  • root["obj"].toStyledString() returns {"str":"Hi"}
  • root["obj"]["str"] returns Hi! because root["obj"] is an object whose str property has a value of Hi!

The last example demonstrates how to access nested objects inside a Json::Value. In addition, a value can be added to a Json::Value in the same way that a key/value pair can be added to a C++ map. For example, the "five"/5 pair can be added to root with root["five"] = 5.

5. Writing JSON Data

The Json::Writer class has only one public function, write, which accepts a Json::Value and returns a std::string. This function is virtual, so if you want to invoke write, you need to use one of the two subclasses of Json::Writer:

  1. Json::FastWriter - outputs JSON in a single line of compressed text
  2. Json::StyledWriter - outputs JSON on multiple lines as needed, human-friendly

To see how these are used, suppose that root is a Json::Value whose data consists of { "num": 1, "obj": { "str": "Hi" }}. If writer is a Json::FastWriter, the function writer.write(root) returns the string {"num":1,"obj":{"str":"Hi"}}. The only other function of Json::FastWriter is enableYAMLCompatibility(), which ensures that every colon will be followed by a space.

If writer is a Json::StyledWriter, the function writer.write(root) returns the following string:

{
   "num" : 1,
   "obj" : {
      "str" : "Hi"
   }
}

As shown, the Json::StyledWriter prints each name/value pair on a new line. Also, each nested value is indented. If a value is an empty object, the writer will print {} without indentation or line break.

In addition to the Json::FastWriter and Json::StyledWriter, JsonCpp also provides a class called Json::StyledStreamWriter. This isn't a subclass of Json::Writer, but it does have a write function:

write(std::basic_ostream<char, std::char_traits<char>>& out, const Json::Value& root);

This writes the data in the Json::Value to the given output stream. This is particularly helpful when you want to write data to a file, and the following section demonstrates how this can be done.

6. Example Application

This article provides an example project that demonstrates how the JsonCpp toolset can be used to read, process, and write JSON data. The jsoncpp_demo.zip archive contains a file called jsoncpp_demo.cpp. Its source code is given as follows:

#include <cstdlib>
#include <fstream>
#include <iostream>
#include "json/json.h"

int main(void) {
  Json::Reader reader;
  Json::Value root;
  Json::StyledStreamWriter writer;
  std::string text = "{ \"first\": \"James\", \"last\": \"Bond\", \"nums\": [0, 0, 7] }";
  std::ofstream outFile;
  
  // Parse JSON and print errors if needed
  if(!reader.parse(text, root)) {
    std::cout << reader.getFormattedErrorMessages();
    exit(1);
  } else {
    
    // Read and modify the json data
    std::cout << "Size: " << root.size() << std::endl;
    std::cout << "Contains nums? " << root.isMember("nums") << std::endl;
    root["first"] = "Jimmy";
    root["middle"] = "Danger";
    
    // Write the output to a file
    outFile.open("output.json");
    writer.write(outFile, root);
    outFile.close();    
  }
  return 0;
}

The application starts by parsing data in a string. If any errors are encountered, getFormattedErrorMessages prints the errors to standard output and the application exits.

If parse completes successfully, the application prints the size of the Json::Value and checks if it contains a member called nums. Then it changes the value corresponding to the first key and adds a value corresponding to the middle key.

After processing the JSON data, the application opens an output stream for a file called output.json. Then it calls the write function of a Json::StyledStreamWriter to print the JSON data to the file.

The project contains a Makefile for building jsoncpp_demo. This expects that the JsonCpp library, libjsoncpp.a, is located in the /home/Matt/jsoncpp/lib directory. It also expects the required headers to be in the /home/Matt/jsoncpp/include directory. To build the demo on your development system, these locations must be changed.

History

5/27/2016 - Initial article submission, added example code to the article, fixed the subtitle

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)