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:
- nlohmann/json: A single-header library for JSON parsing and serialization.
- RapidJSON: A fast JSON parser/generator for C++ with both SAX and DOM style APIs.
- 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++:
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
- 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;
}
- 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;
}
- 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;
}
- 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;
}
- 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;
}
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.
-
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.
- The primary class provided by the library is
-
json::value_t
: Enum class representing the different JSON types (null
,boolean
,number_integer
,number_unsigned
,number_float
,object
,array
,string
). -
json::iterator
andjson::const_iterator
: Iterators for traversing JSON arrays and objects. -
json::reverse_iterator
andjson::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
andjson::const_reference
: Reference types for JSON values.
-
Default constructor:
json()
: Constructs an empty JSON value of typenull
. -
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.
-
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.
-
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)
-
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
-
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)
-
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.
-
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()
: 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.
-
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::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)
-
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.
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:
#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;
}
-
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.
- The
-
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 previouscatch
blocks.
-
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.
-
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. - Buffering Data: Read data byte-by-byte from the TCP buffer and accumulate it into a custom buffer.
- State Management: Maintain a state to track whether you are currently within a JSON message or outside it.
- 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).
#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;
}
-
Socket Creation:
-
socket(AF_INET, SOCK_STREAM, 0)
creates a TCP socket.
-
-
Binding:
-
bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr))
binds the socket to a specific address and port (12345).
-
-
Listening:
-
listen(server_socket, 5)
puts the socket in a listening state, allowing it to accept incoming connections.
-
-
Accepting Connections:
-
accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len)
accepts a new client connection.
-
-
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 usingrecv
. - JSON messages are detected based on
{
and}
characters, withbrace_count
used to keep track of nested braces. - Non-JSON data is simply printed out.
-
-
JSON Parsing:
- JSON messages are parsed using the nlohmann/json library.
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.
-
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.