Post

C++ JSON

Logo

This guide will provide step-by-step instructions on how to add JSON to the Visual Studio C++ project.

In computing, serialization is the process of translating a data structure or object state into a format that can be stored or transmitted and reconstructed later. The reverse process is called deserialization. The game engine will use JSON to deserialize file(s) that define game objects in the game level.

About JSON

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others. These properties make JSON an ideal data-interchange language.

Download JSON

Download
  • Copy the .zip into the ThirdParty folder.
  • Extract the rapidjson-master.zip file.
    • Make sure the contents of the rapidjson files are under this directory and not under another subdirectory.
  • Rename the extracted folder “rapidjson”.
Zip
  • Delete the rapidjson-master.zip file, it is not needed.

Add JSON to the Solution Project(s)

If the Solution contains multiple Projects, the following steps will need to be done for each project. This is because each project needs the path to the JSON includes.

Projects

In the Project Properties, make sure that the Configuration is set to All Configurations and Platform is set to All Platforms.

Configurations
  • Add the directory of the rapidjson include folder to the Additional Include Directories.
    • Additional Include Directories is located in C/C++>General.
    • Add $(SolutionDir)ThirdParty\rapidjson\include
1
$(SolutionDir)ThirdParty\rapidjson\include
Include

Create JSON File

  • In the Build/Assets folder create a folder called data and a text file called data.json.
File
  • Open the file and add JSON data. Use your own values for name and age.
    1
    2
    3
    4
    5
    6
    7
    8
    
    {
      "name": "Raymond",
      "age": 44,
      "speed": 18.5,
      "isAwake": true,
      "position": [10, 20],
      "color": [1, 0, 0]
    }
    
  • The data is in a key, value format .
    • “key”: value
  • Using the key, the value can be retrieved.

It is common to have the JSON file not correctly formatted. Use this page to verify your JSON file: https://jsonformatter.curiousconcept.com/

  • Copy the contents of the JSON file and paste it into the page.
  • Click Process.
  • It will notify of any existing errors.
Formatter

Many errors come from JSON files that are not properly formatted. Use the JSON formatter to validate the JSON files. The JSON formatter allows you to copy from the processed JSON text. These can be pasted back into the JSON file.

Create JSON Functions

  • Create a Json.h and Json.cpp in the Engine project.
Json

Create JSON header (.h)

  • In the Json.h file, add code for the function declarations to load and read the JSON file.
    • The functions are placed in a namespace to keep the function names in their own space <your namespace::json>
1
2
3
4
5
6
7
8
9
10
11
#pragma once
#include <rapidjson/document.h>
#include <string>

namespace nu::json
{
	bool Load(const std::string& filename, rapidjson::Document& document);

	// read json data
	bool Read(const rapidjson::Value& value, const std::string& name, int& data);
}

Create JSON source file (.cpp)

  • In the Json.cpp file, add code for the function definitions to load and read the JSON file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "pch.h"
#include "Json.h"
#include "File.h"

#include <rapidjson/istreamwrapper.h>
#include <rapidjson/error/en.h>
#include <iostream>

namespace nu::json
{
    bool Load(const std::string& filename, rapidjson::Document& document) 
    {
        // read the file into a string
        std::string buffer;
        if (!ReadTextFile(filename, buffer)) 
        {
            std::cerr << "Could not read file:" << filename << std::endl;
            return false;
        }

        // convert the string into a json stream
        std::stringstream stream(buffer);
        rapidjson::IStreamWrapper istream(stream);

        // set the json document from the stream
        document.ParseStream(istream);

        // check if the parse was successful
        if (document.HasParseError())
        {
            std::cerr << "Could not parse JSON: " << filename << std::endl;
            std::cerr << "Error: " << rapidjson::GetParseError_En(document.GetParseError()) << std::endl;

            return false;
        }

        // check that the root value is an object, not an array/string/number/etc.
        if (!document.IsObject())
        {
            std::cerr << "JSON root is not an object: " << filename << std::endl;
            return false;
        }

        return true;
    }

    bool Read(const rapidjson::Value& value, const std::string& name, int& data) 
    {
        // check if the value has the "<name>" and the correct data type
        if (!value.HasMember(name.c_str()) || !value[name.c_str()].IsInt()) 
        {
            std::cerr << "Could not read JSON value (int):" << name << std::endl;
            return false;
        }

        // get the data
        data = value[name.c_str()].GetInt();

        return true;
    }
}
  • Include the Json.h in the Engine.h.
1
#include Json.h"

Load and Read JSON in Main()

  • In the Main.cpp main() function, add the code to read the JSON data.
    • Place this inside main() and towards the top of the function, after setting the Assets directory.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// load the json data from a file
std::string buffer;
if (ReadTextFile("data/data.json", buffer))
{
    // show the contents of the json file (debug)
    std::cout << buffer << std::endl;

    // create json document from the json file contents
    rapidjson::Document document;
    if (json::Load("data/data.json", document))
    {
        // read the age data (int) from the json
        int age;
        json::Read(document, "age", age);
        // show the age data
        std::cout << age << std::endl;
    }
}

+ After running the program, the console will display the contents of the **JSON** file and the **age** data.
<div align="left">
<img src="json-output.jpg" alt="Output" width="80%"/>
</div>

### Add Addition JSON Functions ###
_Add additional functions to load different data types from the **JSON** file._

+ In the Json.h file, _add_ the following functions.  
  + Add new functions to load **float**, **bool**, **std::string**, **vec2**, and **vec3**
  + Include **Math/Vector2.h** and **Math/Vector3.h**

#include “Vector2.h” #include “Vector3.h”

bool Read(const rapidjson::Value& value, const std::string& name, float& data); bool Read(const rapidjson::Value& value, const std::string& name, bool& data); bool Read(const rapidjson::Value& value, const std::string& name, std::string& data); bool Read(const rapidjson::Value& value, const std::string& name, Vector2& data); bool Read(const rapidjson::Value& value, const std::string& name, Vector3& data);

1
2
3
4
5
6
+ Create the definitions for the functions in Json.cpp.
+ The code to get the data for each data type is similar to the integer Read().
  + Change the functions for the data type in Is**DataType**() and Get**DataType**() function, here is an example for the bool data
  + Do this for the **bool**, **float**, and **std::string** Read() functions

bool Read(const rapidjson::Value& value, const std::string& name, bool& data) { // check if the value has the “" and the correct data type if (!value.HasMember(name.c_str()) || !value[name.c_str()].IsBool()) { std::cerr << "Could not read JSON value (bool):" << name << std::endl; return false; }

1
2
3
4
// get the data
data = value[name.c_str()].GetBool();

return true; } ```
  • The Vector2 and Vector3 have multiple values that need to be read.
    • JSON treats these as arrays and need to be read in as an array
  • Here is an example of reading in the Vector2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool Read(const rapidjson::Value& value, const std::string& name, Vector2& data) 
{
    // check if the value has the "<name>" and is an array with 2 elements
    if (!value.HasMember(name.c_str()) || !value[name.c_str()].IsArray() || value[name.c_str()].Size() != 2) 
    {
        std::cerr << "Could not read JSON value (Vector2):" << name << std::endl;
        return false;
    }

    // get json array object
    auto& array = value[name.c_str()];
    // get array values, iterate through each element
    for (rapidjson::SizeType i = 0; i < array.Size(); i++) 
    {
        if (!array[i].IsNumber()) 
        {
            std::cerr << "Could not read JSON value (Vector2):" << name << std::endl;
            return false;
        }

        // get the data
        data[i] = array[i].GetFloat();
    }

    return true;
}
  • Using the Vector2 as an example, complete the Vecto3 Read().
    • The Vector3 has 3 elements.
      • Make sure when checking the array size to check for 3 elements.

Read Data Types in Main()

  • Update the code in main() to read and display all the data types
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// read/show the data from the json file
std::string name;
int age;
float speed;
bool isAwake;
<your namespace>::Vector2 position;
<your namespace>::Vector3 color;

// read the json data
<your namespace>::json::Read(document, "name", name);
<your namespace>::json::Read(document, "age", age);
<your namespace>::json::Read(document, "speed", speed);
<your namespace>::json::Read(document, "isAwake", isAwake);
<your namespace>::json::Read(document, "position", position);
<your namespace>::json::Read(document, "color", color);

// show the data
std::cout << name << " " << age << " " << speed << " " << isAwake << std::endl;
std::cout << position.x << " " << position.y << std::endl;
std::cout << color.r << " " << color.g << " " << color.b << " " << std::endl;
  • The output should look like the image below
Output All

Create JSON Read Macro

To make reading data in easier, a macro can be created to simplify the code. A macro in C++ is a preprocessor directive that defines a code fragment or value to be substituted and expanded before the actual compilation process begins.

  • In the Json.h file, add the following macro.
    • The # in a macro converts the data parameter to a string by putting quotes around the name.
    • Place the macro after the #include lines.
1
#define JSON_READ(value, data) <your namespace>::json::Read(value, #data, data)
  • In main() change the Read() function to the macro.
    • For this macro to work, the key in the JSON file must match the variable name in the code.
    • Place this after the #include lines.

Before

1
<your namespace>::json::Read(document, "name", name);

After

1
JSON_READ(document, name);
  • In main() change all the read functions to use the macro
1
2
3
4
5
6
JSON_READ(document, name);
JSON_READ(document, age);
JSON_READ(document, speed);
JSON_READ(document, isAwake);
JSON_READ(document, position);
JSON_READ(document, color);
  • Build and Run the program again to ensure it is working correctly. It should display as before.
Output All
This post is licensed under CC BY 4.0 by the author.