Introduction To Json Message programming - GitMasterNikanjam/C_WiKi GitHub Wiki

In C++ programming, JSON (JavaScript Object Notation) is often used to exchange data between a client and a server. JSON is a lightweight data interchange format that is easy for humans to read and write, and easy for machines to parse and generate.

To work with JSON in C++, you'll typically use a library to parse JSON strings into a usable format and to serialize data structures back into JSON. Some popular JSON libraries for C++ include:

  1. nlohmann/json: A single-header library for JSON parsing and serialization.
  2. RapidJSON: A fast JSON parser/generator for C++ with both SAX and DOM style APIs.
  3. JsonCpp: A C++ library that allows manipulating JSON values, including serialization and deserialization.

Here’s an example using nlohmann/json to demonstrate how to handle JSON messages in C++:

Installation

To use nlohmann/json, you can install it via a package manager like vcpkg or simply include the single header file in your project.

Using vcpkg:

vcpkg install nlohmann-json

Basic Usage

  1. Parsing JSON from a string:
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main() {
    std::string jsonString = R"({"name": "John", "age": 30, "is_student": false})";

    // Parse the JSON string
    json jsonObj = json::parse(jsonString);

    // Access values
    std::string name = jsonObj["name"];
    int age = jsonObj["age"];
    bool isStudent = jsonObj["is_student"];

    std::cout << "Name: " << name << "\n";
    std::cout << "Age: " << age << "\n";
    std::cout << "Is Student: " << std::boolalpha << isStudent << "\n";

    return 0;
}
  1. Creating and serializing JSON:
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main() {
    // Create a JSON object
    json jsonObj;
    jsonObj["name"] = "John";
    jsonObj["age"] = 30;
    jsonObj["is_student"] = false;

    // Convert JSON object to string
    std::string jsonString = jsonObj.dump();

    std::cout << "JSON String: " << jsonString << "\n";

    return 0;
}
  1. Working with nested JSON objects:
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main() {
    std::string jsonString = R"({
        "name": "John",
        "age": 30,
        "address": {
            "street": "123 Main St",
            "city": "Anytown"
        }
    })";

    // Parse the JSON string
    json jsonObj = json::parse(jsonString);

    // Access nested values
    std::string street = jsonObj["address"]["street"];
    std::string city = jsonObj["address"]["city"];

    std::cout << "Street: " << street << "\n";
    std::cout << "City: " << city << "\n";

    return 0;
}

Using RapidJSON

  1. Parsing JSON from a string:
#include <iostream>
#include <rapidjson/document.h>

int main() {
    const char* jsonString = R"({"name": "John", "age": 30, "is_student": false})";

    rapidjson::Document document;
    document.Parse(jsonString);

    std::string name = document["name"].GetString();
    int age = document["age"].GetInt();
    bool isStudent = document["is_student"].GetBool();

    std::cout << "Name: " << name << "\n";
    std::cout << "Age: " << age << "\n";
    std::cout << "Is Student: " << std::boolalpha << isStudent << "\n";

    return 0;
}
  1. Creating and serializing JSON:
#include <iostream>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>

int main() {
    rapidjson::Document document;
    document.SetObject();

    rapidjson::Document::AllocatorType& allocator = document.GetAllocator();

    document.AddMember("name", "John", allocator);
    document.AddMember("age", 30, allocator);
    document.AddMember("is_student", false, allocator);

    rapidjson::StringBuffer buffer;
    rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
    document.Accept(writer);

    std::cout << "JSON String: " << buffer.GetString() << "\n";

    return 0;
}

Conclusion

Using JSON in C++ involves choosing the right library for your needs and understanding how to parse and serialize JSON data. Each library has its own strengths and may be more suitable for different use cases, such as performance-critical applications (RapidJSON) or ease of use (nlohmann/json).


The nlohmann/json library, also known as JSON for Modern C++, provides a variety of functions and variables to facilitate working with JSON in C++. Below is a detailed overview of the main features, functions, and variables of the library.

Basic Components

  1. json Class:
    • The primary class provided by the library is json, which represents a JSON value.
    • It can hold any JSON data type: null, boolean, number, string, array, or object.

Type Definitions

  • json::value_t: Enum class representing the different JSON types (null, boolean, number_integer, number_unsigned, number_float, object, array, string).
  • json::iterator and json::const_iterator: Iterators for traversing JSON arrays and objects.
  • json::reverse_iterator and json::const_reverse_iterator: Reverse iterators.
  • json::allocator_type: Allocator type for memory management.
  • json::pointer: JSON Pointer type for navigating within a JSON structure.
  • json::reference and json::const_reference: Reference types for JSON values.

Constructors

  • Default constructor: json(): Constructs an empty JSON value of type null.
  • Type constructors: Constructs a JSON value from different C++ types:
    • json(boolean_t value)
    • json(number_integer_t value)
    • json(number_unsigned_t value)
    • json(number_float_t value)
    • json(const char* value)
    • json(const string_t& value)
    • json(const array_t& value)
    • json(const object_t& value)
  • Initializer list constructor: json(std::initializer_list<json> init): Constructs a JSON array or object from an initializer list.

Member Functions

Serialization

  • dump(): Serializes the JSON object to a string.
    • std::string dump(int indent = -1, char indent_char = ' ', bool ensure_ascii = false) const
    • indent: Number of spaces for indentation (default is no indentation).
    • indent_char: Character to use for indentation.
    • ensure_ascii: Escape all non-ASCII characters if true.

Parsing

  • parse(): Static method to parse a JSON string.
    • static json parse(const string_t& s, const parser_callback_t cb = nullptr, const bool allow_exceptions = true, const bool ignore_comments = false)

Accessors

  • at(): Access element with bounds checking.
    • reference at(size_type idx) for arrays.
    • reference at(const typename object_t::key_type& key) for objects.
  • operator[]: Access element without bounds checking.
    • reference operator[](size_type idx) for arrays.
    • reference operator[](const typename object_t::key_type& key) for objects.
  • front(): Access the first element.
  • back(): Access the last element.
  • value(): Get a value with a default if key does not exist.
    • template <class ValueType> ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const

Modifiers

  • clear(): Clears the JSON object or array.
  • insert(): Inserts an element.
    • template <class... Args> std::pair<iterator, bool> insert(const_iterator pos, Args&&... args)
  • emplace(): Constructs elements in-place.
    • template <class... Args> std::pair<iterator, bool> emplace(Args&&... args)
  • push_back(): Adds an element to the end of a JSON array.
  • erase(): Removes elements.
    • iterator erase(iterator pos)
    • size_type erase(const typename object_t::key_type& key)

Iterators

  • begin(): Returns an iterator to the beginning.
  • end(): Returns an iterator to the end.
  • cbegin(): Returns a constant iterator to the beginning.
  • cend(): Returns a constant iterator to the end.
  • rbegin(): Returns a reverse iterator to the beginning.
  • rend(): Returns a reverse iterator to the end.

Capacity

  • empty(): Checks if the JSON object or array is empty.
  • size(): Returns the number of elements.
  • max_size(): Returns the maximum possible number of elements.

Type Inspection

  • type(): Returns the type of the JSON value.
  • is_null(): Checks if the JSON value is null.
  • is_boolean(): Checks if the JSON value is a boolean.
  • is_number(): Checks if the JSON value is a number.
  • is_string(): Checks if the JSON value is a string.
  • is_array(): Checks if the JSON value is an array.
  • is_object(): Checks if the JSON value is an object.

Conversion

  • get(): Converts JSON value to a specified type.
    • template <typename T> T get() const
  • get_to(): Converts JSON value to a specified type and stores it in a given variable.
    • template <typename T> T& get_to(T& v) const
  • get_ptr(): Returns a pointer to the value.
    • template <typename T> T* get_ptr() noexcept
  • get_ref(): Returns a reference to the value.
    • template <typename T> T& get_ref()
  • get_ref() const: Returns a constant reference to the value.

JSON Pointer and JSON Patch

  • json::json_pointer: A class for navigating JSON objects using JSON Pointers.
  • json::patch(): Applies a JSON Patch.
    • static json patch(const json& target, const json& patch)

Exceptions

  • json::exception: Base class for all exceptions thrown by the library.
  • json::parse_error: Exception thrown during parsing.
  • json::type_error: Exception thrown for type mismatches.
  • json::out_of_range: Exception thrown for out-of-range errors.
  • json::other_error: Other exceptions.

Example

Here is a simple example demonstrating some of these features:

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

using json = nlohmann::json;

int main() {
    // Creating a JSON object
    json j;
    j["name"] = "John";
    j["age"] = 30;
    j["is_student"] = false;
    j["address"] = {{"street", "123 Main St"}, {"city", "Anytown"}};

    // Serializing to a string
    std::string serialized = j.dump(4); // Pretty print with 4 spaces
    std::cout << "Serialized JSON:\n" << serialized << std::endl;

    // Parsing from a string
    std::string jsonString = R"({"name": "Jane", "age": 25, "is_student": true})";
    json j2 = json::parse(jsonString);

    // Accessing values
    std::string name = j2["name"];
    int age = j2["age"];
    bool is_student = j2["is_student"];

    std::cout << "Name: " << name << "\nAge: " << age << "\nIs student: " << std::boolalpha << is_student << std::endl;

    // Modifying values
    j2["name"] = "Doe";
    j2["address"] = {{"street", "456 Elm St"}, {"city", "Othertown"}};

    // Iterating over a JSON object
    for (auto& el : j2.items()) {
        std::cout << el.key() << " : " << el.value() << "\n";
    }

    return 0;
}

This example demonstrates creating a JSON object, serializing it to a string, parsing a JSON string, accessing and modifying values, and iterating over JSON object elements.


Using try and catch blocks in C++ helps handle exceptions gracefully, preventing the program from crashing when an error occurs. When working with the nlohmann/json library, exceptions can be thrown during parsing, accessing elements, or performing invalid operations. These exceptions can be caught and handled using try and catch blocks.

Here's an example demonstrating how to use try and catch with nlohmann/json to prevent errors:

Example Code

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

using json = nlohmann::json;

int main() {
    std::string jsonString = R"({"name": "John", "age": "thirty", "is_student": false})"; // Note the invalid age

    try {
        // Parsing JSON from a string
        json jsonObj = json::parse(jsonString);
        std::cout << "Parsed JSON successfully!\n";

        // Accessing values
        std::string name = jsonObj.at("name");
        int age = jsonObj.at("age"); // This line will throw an exception
        bool isStudent = jsonObj.at("is_student");

        std::cout << "Name: " << name << "\n";
        std::cout << "Age: " << age << "\n";
        std::cout << "Is Student: " << std::boolalpha << isStudent << "\n";
    } catch (const json::parse_error& e) {
        std::cerr << "Parse error: " << e.what() << std::endl;
    } catch (const json::type_error& e) {
        std::cerr << "Type error: " << e.what() << std::endl;
    } catch (const json::out_of_range& e) {
        std::cerr << "Out of range error: " << e.what() << std::endl;
    } catch (const json::exception& e) {
        std::cerr << "Other JSON error: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Standard exception: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "Unknown error occurred" << std::endl;
    }

    std::cout << "Program continues after error handling.\n";

    return 0;
}

Explanation

  1. try Block:

    • The try block contains code that may throw an exception. In this example, it includes parsing a JSON string and accessing its elements.
  2. catch Blocks:

    • catch (const json::parse_error& e): Catches exceptions related to JSON parsing errors.
    • catch (const json::type_error& e): Catches exceptions related to type mismatches (e.g., trying to convert a string to an integer).
    • catch (const json::out_of_range& e): Catches exceptions related to accessing elements that are out of range.
    • catch (const json::exception& e): Catches any other exceptions specific to the JSON library.
    • catch (const std::exception& e): Catches standard exceptions not specific to the JSON library.
    • catch (...): Catches any other exceptions not covered by the previous catch blocks.

Additional Example with Function

Here’s another example where a function parses a JSON string and handles exceptions internally:

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

using json = nlohmann::json;

json parseJson(const std::string& jsonString) {
    try {
        json jsonObj = json::parse(jsonString);
        return jsonObj;
    } catch (const json::parse_error& e) {
        std::cerr << "Parse error: " << e.what() << std::endl;
    } catch (const json::type_error& e) {
        std::cerr << "Type error: " << e.what() << std::endl;
    } catch (const json::out_of_range& e) {
        std::cerr << "Out of range error: " << e.what() << std::endl;
    } catch (const json::exception& e) {
        std::cerr << "Other JSON error: " << e.what() << std::endl;
    } catch (const std::exception& e) {
        std::cerr << "Standard exception: " << e.what() << std::endl;
    } catch (...) {
        std::cerr << "Unknown error occurred" << std::endl;
    }

    // Return an empty JSON object in case of error
    return json();
}

int main() {
    std::string jsonString = R"({"name": "John", "age": "thirty", "is_student": false})"; // Note the invalid age

    json jsonObj = parseJson(jsonString);

    // Check if parsing was successful
    if (!jsonObj.empty()) {
        std::cout << "Parsed JSON successfully!\n";
    } else {
        std::cout << "Failed to parse JSON.\n";
    }

    std::cout << "Program continues after error handling.\n";

    return 0;
}

In this example, the parseJson function handles exceptions internally and returns an empty JSON object if an error occurs. This approach helps isolate error handling within specific functions, making the main code cleaner and more readable.


Reading byte-by-byte from a TCP buffer and checking for specific acknowledgment characters to determine the start and end of a JSON message can be a good approach, especially when messages might be interleaved or if they contain non-JSON data. This method allows you to delineate JSON messages accurately and avoid parsing errors.

Implementation Strategy

  1. Define Start and End Markers: Decide on specific characters or sequences to mark the beginning and end of JSON messages. Common choices are { and } for JSON objects or [ and ] for JSON arrays.
  2. Buffering Data: Read data byte-by-byte from the TCP buffer and accumulate it into a custom buffer.
  3. State Management: Maintain a state to track whether you are currently within a JSON message or outside it.
  4. Parse JSON: Once a complete JSON message is accumulated, parse it using the nlohmann/json library.

If you prefer not to use the Boost.Asio library, you can use the standard C++ socket programming APIs provided by the operating system. For example, on Unix-like systems, you can use the POSIX sockets API, and on Windows, you can use the Winsock API.

Below is an example of a TCP server that reads data byte-by-byte using the POSIX sockets API on Unix-like systems (Linux, macOS).

TCP Server Example using POSIX Sockets

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

void handle_client(int client_socket) {
    try {
        char buffer[1];
        std::string custom_buffer;
        bool inside_json = false;
        int brace_count = 0;

        while (true) {
            ssize_t bytes_received = recv(client_socket, buffer, 1, 0);

            if (bytes_received < 0) {
                std::cerr << "Error reading from socket" << std::endl;
                close(client_socket);
                return;
            } else if (bytes_received == 0) {
                std::cout << "Client disconnected" << std::endl;
                close(client_socket);
                return;
            }

            char c = buffer[0];

            // Check for the start of a JSON object
            if (c == '{' && !inside_json) {
                inside_json = true;
                brace_count = 1;
                custom_buffer += c;
            } else if (inside_json) {
                custom_buffer += c;
                if (c == '{') {
                    brace_count++;
                } else if (c == '}') {
                    brace_count--;
                    if (brace_count == 0) {
                        // End of JSON message
                        inside_json = false;

                        try {
                            json jsonObj = json::parse(custom_buffer);
                            std::cout << "Received JSON message: " << jsonObj.dump(4) << std::endl;
                            // Handle the JSON message here
                        } catch (json::parse_error& e) {
                            std::cerr << "Failed to parse JSON message: " << e.what() << std::endl;
                        }

                        custom_buffer.clear();
                    }
                }
            } else {
                // Handle non-JSON data
                std::cout << "Received non-JSON data: " << c << std::endl;
            }
        }
    } catch (std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

int main() {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket < 0) {
        std::cerr << "Error creating socket" << std::endl;
        return 1;
    }

    sockaddr_in server_addr;
    std::memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(12345);

    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        std::cerr << "Error binding socket" << std::endl;
        close(server_socket);
        return 1;
    }

    if (listen(server_socket, 5) < 0) {
        std::cerr << "Error listening on socket" << std::endl;
        close(server_socket);
        return 1;
    }

    std::cout << "Server is running on port 12345..." << std::endl;

    while (true) {
        sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_socket < 0) {
            std::cerr << "Error accepting connection" << std::endl;
            close(server_socket);
            return 1;
        }

        std::thread(handle_client, client_socket).detach();
    }

    close(server_socket);
    return 0;
}

Explanation

  1. Socket Creation:

    • socket(AF_INET, SOCK_STREAM, 0) creates a TCP socket.
  2. Binding:

    • bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) binds the socket to a specific address and port (12345).
  3. Listening:

    • listen(server_socket, 5) puts the socket in a listening state, allowing it to accept incoming connections.
  4. Accepting Connections:

    • accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len) accepts a new client connection.
  5. Handling Client Connections:

    • handle_client(int client_socket) function runs in a separate thread to handle each client connection.
    • Inside handle_client, data is read byte-by-byte using recv.
    • JSON messages are detected based on { and } characters, with brace_count used to keep track of nested braces.
    • Non-JSON data is simply printed out.
  6. JSON Parsing:

    • JSON messages are parsed using the nlohmann/json library.

Compiling the Code

To compile this code, you can use a C++ compiler like g++:

g++ -std=c++11 -pthread -o tcp_server tcp_server.cpp -ljsoncpp

Replace tcp_server.cpp with the name of your source file.

Considerations

  • Portability: This example uses POSIX sockets, which are available on Unix-like systems (Linux, macOS). For Windows, you would need to use the Winsock API and include <winsock2.h>.
  • Error Handling: The example includes basic error handling. In a production application, you would likely need more robust error handling and possibly reconnection logic.
  • Thread Safety: The std::thread library is used to handle multiple clients concurrently. Ensure thread safety when accessing shared resources.

This approach avoids external libraries and relies solely on standard C++ and POSIX sockets for network communication.

⚠️ **GitHub.com Fallback** ⚠️