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:
- Change to the directory containing the JsonCpp source code and create a directory to hold the build files:
mkdir -p build/debug
- Change to the new directory:
cd build/debug
- Run CMake:
cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON \
-DBUILD_SHARED_LIBS=OFF -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../..
- 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::Value
s.
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::Value
s. 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
:
Json::FastWriter
- outputs JSON in a single line of compressed text 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;
if(!reader.parse(text, root)) {
std::cout << reader.getFormattedErrorMessages();
exit(1);
} else {
std::cout << "Size: " << root.size() << std::endl;
std::cout << "Contains nums? " << root.isMember("nums") << std::endl;
root["first"] = "Jimmy";
root["middle"] = "Danger";
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