C++使用nlohmann/json库解析JSON数据应用示例
本章节介绍了如何在C++项目中使用nlohmann/json库来解析和处理JSON数据。我们将展示如何安装和配置该库,并通过封装的JsonHandler类来简化JSON数据的操作过程。
1. nlohmann/json库介绍
nlohmann/json 是一个用于现代C++的JSON库,由Niels Lohmann开发。它是一个单头文件库,使用C++11标准编写,具有以下特点:
- 简洁的API:语法直观,像访问原生C++数据结构一样操作JSON
- 类型安全:提供了类型检查和异常处理机制
- 高性能:比许多其他JSON库更快
- 零依赖:只需要包含一个头文件即可使用
- 跨平台:支持Windows、Linux、macOS等操作系统
1.1 下载安装
nlohmann/json库可以通过多种方式获取和安装:
-
通过包管理器安装:
- vcpkg:
vcpkg install nlohmann-json - Conan:
conan install nlohmann_json/3.11.2 - apt (Ubuntu/Debian):
sudo apt install nlohmann-json3-dev
- vcpkg:
-
手动下载:
- 访问GitHub仓库:github.com/nlohmann/js...
- 下载单头文件版本:github.com/nlohmann/js...
- 将
json.hpp文件复制到项目目录中
1.2 配置方法
在项目中使用nlohmann库非常简单:
-
包含头文件:
cpp#include <nlohmann/json.hpp> // 或者使用别名 using json = nlohmann::json; -
CMake配置(如果通过包管理器安装):
cmakefind_package(nlohmann_json CONFIG REQUIRED) target_link_libraries(main PRIVATE nlohmann_json::nlohmann_json) -
手动配置:
- 将
json.hpp文件放在项目目录中 - 在代码中包含:
#include "nlohmann/json.hpp"
- 将
在本项目中,我们直接将json.hpp文件包含在项目中,无需额外配置。
2. 应用场景
在本项目中,nlohmann库主要应用于以下场景:
2.1 解析HTTP响应中的JSON数据
当客户端向服务器发送GET或POST请求后,服务器返回的数据通常是JSON格式。我们需要使用nlohmann/json库来解析这些数据,提取有用信息。
2.2 格式化JSON数据用于展示
为了在WebView中更好地展示JSON数据,我们需要将其格式化为带有语法高亮的HTML格式。
2.3 构造JSON请求数据
在向服务器发送POST请求时,我们需要构造JSON格式的请求体数据。
2.4 处理文件列表等结构化数据
服务器返回的文件列表等信息通常以JSON数组形式提供,需要使用nlohmann库进行解析和处理。
3. 使用JsonHandler封装nlohmann/json库相关方法
为了更好地管理和重用JSON处理代码,我们创建了JsonHandler类来封装所有与nlohmann/json库相关的操作。
3.1 JsonHandler.h头文件
cpp
#ifndef JSONHANDLER_H
#define JSONHANDLER_H
#include <wx/string.h>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
class JsonHandler {
public:
JsonHandler();
~JsonHandler();
// JSON处理相关方法
void ParseJsonResponse(const std::string& response);
wxString FormatJsonForHtml(const wxString& jsonStr, const bool& formatBit);
wxString FormatJsonValue(const json& j);
wxString FormatJsonValueFormat(const json& j, int indentLevel);
wxString GenerateJsonHtml(const wxString& jsonResponse, const bool& formatBit);
// 保存原始JSON数据
void SetRawJsonData(const wxString& rawData) { m_rawJsonData = rawData; }
wxString GetRawJsonData() const { return m_rawJsonData; }
private:
wxString m_rawJsonData; // 保存原始JSON数据
};
#endif // JSONHANDLER_H
3.2 JsonHandler.cpp实现文件
JsonHandler.cpp文件中实现了所有JSON处理方法:
- ParseJsonResponse:解析JSON响应数据
- FormatJsonForHtml:将JSON格式化为HTML显示格式
- FormatJsonValue:递归生成不带缩进的JSON HTML字符串
- FormatJsonValueFormat:递归生成带缩进的JSON HTML字符串
- GenerateJsonHtml:生成完整的JSON显示HTML页面
下面是这些方法的具体实现:
3.2.1 ParseJsonResponse方法实现
cpp
// 解析 JSON 响应
void JsonHandler::ParseJsonResponse(const std::string& response) {
// 尝试解析 JSON
try {
// 使用nlohmann库解析JSON字符串
json j = json::parse(response);
// 将 JSON 格式化为带缩进的字符串
std::string json_str = j.dump(4);
// 记录日志
wxLogInfo(wxString::FromUTF8("JSON: %s"), wxString::FromUTF8(json_str.c_str()));
// 检查是否存在 "message" 字段
if (j.contains("message")) {
// 获取message字段的值
std::string message = j["message"];
// 记录解析结果到日志
wxLogInfo(wxString::FromUTF8("解析结果: %s"), wxString::FromUTF8(message.c_str()));
} else {
// 如果缺少message字段,记录错误日志
wxLogInfo(wxString::FromUTF8("JSON格式错误: 缺少message字段"));
}
// 检查是否存在 "data" 数组
if (j.contains("data") && j["data"].is_array()) {
// 获取data数组
json dataArray = j["data"];
// 遍历数组中的每个元素
for (const auto& item : dataArray) {
// 检查元素是否包含name和age字段且类型正确
if (item.contains("name") && item["name"].is_string() && item.contains("age") && item["age"].is_number()) {
// 提取name和age字段的值
std::string name = item["name"];
int age = item["age"];
// 在日志中显示name和age信息
wxLogMessage(wxString::FromUTF8("Name: %s, Age: %d"), wxString::FromUTF8(name.c_str()), age);
}
}
} else {
// 如果缺少data数组或格式不正确,记录错误日志
wxLogInfo(wxString::FromUTF8("JSON格式错误: 缺少data数组或格式不正确"));
}
} catch (const json::parse_error& e) {
// JSON 解析失败
std::string parseErrorMsg = "JSON解析失败: " + std::string(e.what());
wxLogError(wxString::FromUTF8(parseErrorMsg.c_str()));
}
}
3.2.2 FormatJsonForHtml方法实现
cpp
// 格式化JSON字符串用于HTML显示
wxString JsonHandler::FormatJsonForHtml(const wxString& jsonStr, const bool& formatBit) {
try {
// 确保使用UTF-8编码
std::string utf8Json = std::string(jsonStr.ToUTF8());
// 使用nlohmann库解析JSON字符串
json j = json::parse(utf8Json);
wxLogError("***************************************************");
wxLogError(j.dump());
wxLogError("***************************************************");
// 递归生成带有HTML类名的JSON字符串
wxString formattedJson;
if (!formatBit) {
// 使用不带缩进的方式格式化JSON
formattedJson = FormatJsonValue(j);
} else {
// 使用带缩进的方式格式化JSON
formattedJson = FormatJsonValueFormat(j, 0);
}
wxLogError(wxT("格式化JSON字符串成功!"));
// 返回格式化后的JSON字符串
return formattedJson;
} catch (const json::parse_error& e) {
// 记录JSON解析错误日志
wxLogError("JSON Parse Err: %s", e.what());
wxLogError(wxT("格式化JSON字符串失败!"));
// 返回原始JSON字符串
return jsonStr;
}
}
3.2.3 FormatJsonValue方法实现
cpp
// 递归生成带有HTML类名的JSON字符串(不带格式化)
wxString JsonHandler::FormatJsonValue(const json& j) {
// 判断JSON值是否为对象类型
if (j.is_object()) {
// 如果是 JSON 对象
wxString result = wxT("{");
bool first = true;
// 遍历对象中的每个键值对
for (auto& [key, value] : j.items()) {
if (!first) {
// 如果不是第一个元素,添加逗号分隔符
result += wxT(",");
}
first = false;
// 添加键,使用特定CSS类名进行标记
result += wxString::Format(wxT("<span class='json-key'>\"%s\"</span>: "), wxString::FromUTF8(key.c_str()));
// 递归处理值
result += FormatJsonValue(value);
}
result += wxT("}");
wxLogError("----------------------------------------------------------------------");
wxLogError(result);
wxLogError("----------------------------------------------------------------------");
// 返回格式化后的对象字符串
return result;
} else if (j.is_array()) {
// 如果是 JSON 数组
wxString result = wxT("[");
bool first = true;
// 遍历数组中的每个元素
for (auto& item : j) {
if (!first) {
// 如果不是第一个元素,添加逗号分隔符
result += wxT(",");
}
first = false;
// 递归处理数组项
result += FormatJsonValue(item);
}
result += wxT("]");
// 返回格式化后的数组字符串
return result;
} else if (j.is_string()) {
// 如果是 JSON 字符串
std::string jsonString = j.get<std::string>();
// 使用 wxString::FromUTF8 正确处理UTF-8编码的字符串
wxString wxStringValue = wxString::FromUTF8(jsonString.c_str());
wxLogMessage("字符串值: %s", wxStringValue);
// 转义HTML特殊字符以防止在HTML中显示错误
wxString escapedValue = wxStringValue;
escapedValue.Replace("&", "&"); // 转义&符号
escapedValue.Replace("<", "<"); // 转义<符号
escapedValue.Replace(">", ">"); // 转义>符号
escapedValue.Replace("\"", """); // 转义双引号
escapedValue.Replace("'", "'"); // 转义单引号
// 使用特定CSS类名标记字符串值并返回
return wxString::Format("<span class='json-string'>\"%s\"</span>", escapedValue);
} else if (j.is_number_integer() || j.is_number_unsigned()) {
// 如果是 JSON 数字(整数)
// 使用特定CSS类名标记整数并返回
return wxString::Format(wxT("<span class='json-number'>%lld</span>"), j.get<int64_t>());
} else if (j.is_number_float()) {
// 如果是 JSON 数字(浮点数)
// 使用特定CSS类名标记浮点数并返回
return wxString::Format(wxT("<span class='json-number'>%.6f</span>"), j.get<double>());
} else if (j.is_boolean()) {
// 如果是 JSON 布尔值
// 根据布尔值返回true或false,并使用特定CSS类名标记
return j.get<bool>() ? wxT("<span class='json-boolean'>true</span>") : wxT("<span class='json-boolean'>false</span>");
} else if (j.is_null()) {
// 如果是 JSON null
// 使用特定CSS类名标记null值并返回
return wxT("<span class='json-null'>null</span>");
}
// 对于其他类型,使用特定CSS类名标记并返回
return wxString::Format("<span class='json-string'>%s</span>", j.dump().c_str());
}
3.2.4 FormatJsonValueFormat方法实现
cpp
// 递归生成带有HTML类名的JSON字符串(带格式化和缩进)
wxString JsonHandler::FormatJsonValueFormat(const json& j, int indentLevel) {
// 生成当前层级的缩进字符串(每级2个空格)
wxString indent(indentLevel * 2, ' ');
// 生成内层缩进字符串(比当前层级多一级)
wxString indentInner((indentLevel + 1) * 2, ' ');
// 判断JSON值是否为对象类型
if (j.is_object()) {
// 如果是 JSON 对象
if (j.empty()) {
// 如果对象为空,直接返回{}
return wxT("{}");
}
wxString result = wxT("{\n");
bool first = true;
// 遍历对象中的每个键值对
for (auto& [key, value] : j.items()) {
if (!first) {
// 如果不是第一个元素,添加逗号和换行符
result += wxT(",\n");
}
first = false;
// 添加键和值,带缩进
result += indentInner + wxString::Format(
wxT("<span class='json-key'>\"%s\"</span>: %s"),
wxString::FromUTF8(key.c_str()),
// 递归处理值,缩进层级加1
FormatJsonValueFormat(value, indentLevel + 1)
);
}
// 添加换行符、缩进和结束括号
result += wxT("\n") + indent + wxT("}");
// 返回格式化后的对象字符串
return result;
} else if (j.is_array()) {
// 如果是 JSON 数组
if (j.empty()) {
// 如果数组为空,直接返回[]
return wxT("[]");
}
wxString result = wxT("[\n");
bool first = true;
// 遍历数组中的每个元素
for (auto& item : j) {
if (!first) {
// 如果不是第一个元素,添加逗号和换行符
result += wxT(",\n");
}
first = false;
// 添加数组项,带缩进
result += indentInner + FormatJsonValueFormat(item, indentLevel + 1);
}
// 添加换行符、缩进和结束括号
result += wxT("\n") + indent + wxT("]");
// 返回格式化后的数组字符串
return result;
} else if (j.is_string()) {
// 如果是 JSON 字符串
std::string jsonString = j.get<std::string>();
// 使用 wxString::FromUTF8 正确处理UTF-8编码的字符串
wxString wxStringValue = wxString::FromUTF8(jsonString.c_str());
wxLogMessage("字符串值: %s", wxStringValue);
// 转义HTML特殊字符以防止在HTML中显示错误
wxString escapedValue = wxStringValue;
escapedValue.Replace("&", "&"); // 转义&符号
escapedValue.Replace("<", "<"); // 转义<符号
escapedValue.Replace(">", ">"); // 转义>符号
escapedValue.Replace("\"", """); // 转义双引号
escapedValue.Replace("'", "'"); // 转义单引号
// 使用特定CSS类名标记字符串值并返回
return wxString::Format("<span class='json-string'>\"%s\"</span>", escapedValue);
} else if (j.is_number_integer() || j.is_number_unsigned()) {
// 如果是 JSON 数字(整数)
// 使用特定CSS类名标记整数并返回
return wxString::Format(wxT("<span class='json-number'>%lld</span>"), j.get<int64_t>());
} else if (j.is_number_float()) {
// 如果是 JSON 数字(浮点数)
// 使用特定CSS类名标记浮点数并返回
return wxString::Format(wxT("<span class='json-number'>%.6f</span>"), j.get<double>());
} else if (j.is_boolean()) {
// 如果是 JSON 布尔值
// 根据布尔值返回true或false,并使用特定CSS类名标记
return j.get<bool>() ? wxT("<span class='json-boolean'>true</span>") : wxT("<span class='json-boolean'>false</span>");
} else if (j.is_null()) {
// 如果是 JSON null
// 使用特定CSS类名标记null值并返回
return wxT("<span class='json-null'>null</span>");
}
// 对于其他类型,使用特定CSS类名标记并返回
return wxString::Format("<span class='json-string'>%s</span>", j.dump().c_str());
}
3.2.5 GenerateJsonHtml方法实现
cpp
// 生成 HTML 页面
wxString JsonHandler::GenerateJsonHtml(const wxString& jsonResponse, const bool& formatBit) {
wxLogError("jsonResponse:---------------------------------------");
wxLogError(jsonResponse);
wxLogError("jsonResponse:---------------------------------------");
// 格式化JSON数据
wxString formattedJson = FormatJsonForHtml(jsonResponse, formatBit);
// 构造完整的 HTML 字符串
wxString html = wxString::Format(
R"(<!DOCTYPE html>
<html>
<head>
<meta charset='UTF-8'>
<title>JSON响应</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
}
.json-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
overflow-x: auto;
}
h1 {
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-top: 0;
}
pre {
font-family: 'Consolas', 'Courier New', monospace;
font-size: 14px;
line-height: 1.4;
margin: 0;
white-space: pre-wrap; /* 保留空格和换行 */
}
.json-key { color: #e74c3c; font-weight: bold; }
.json-string { color: #27ae60; }
.json-number { color: #3498db; font-weight: bold; }
.json-boolean { color: #9b59b6; font-weight: bold; }
.json-null { color: #95a5a6; font-weight: bold; }
.toggle { cursor: pointer; }
</style>
<script src="./static/highlight.min.js"></script>
<link rel="stylesheet" href="./static/default.min.css">
<script>
document.addEventListener('DOMContentLoaded', function() {
hljs.highlightAll();
const toggles = document.querySelectorAll('.toggle');
toggles.forEach(toggle => {
toggle.addEventListener('click', function() {
const content = this.nextElementSibling;
if (content.style.display === 'none') {
content.style.display = 'block';
} else {
content.style.display = 'none';
}
});
});
});
</script>
</head>
<body>
<h1>服务器响应</h1>
<div class='json-container'><pre>%s</pre></div>
</body>
</html>)",
formattedJson
);
// 日志输出,用于调试
wxLogInfo(wxString::FromUTF8("生成的完整HTML:\n%s"), html);
// 返回生成的HTML页面
return html;
}
4. MyFrame.cpp调用JsonHandler中封装的各个方法示例
在主框架类MyFrame中,我们通过jsonHandler实例调用JsonHandler类中封装的方法。
4.1 示例1:处理GET请求返回的JSON数据
cpp
// 获取文件列表的事件处理函数
void MyFrame::OnGetFileLists(wxCommandEvent& event) {
// 发送GET请求获取文件列表
std::string url = "/files";
std::string response = httpClient.HttpGet(url);
// 检查响应是否为空
if (response.empty()) {
// 如果返回的响应为空,可能是请求失败
wxLogError("GET request failed or returned empty response.");
m_textCtrl->SetValue("GET Request Failed: Empty response.");
return;
}
// 保存原始JSON数据供后续使用
jsonHandler.SetRawJsonData(wxString::FromUTF8(response.c_str()));
// 在浏览器控件中显示格式化的JSON数据
wxString html = jsonHandler.GenerateJsonHtml(wxString::FromUTF8(response.c_str()), false);
m_webView->SetPage(html, "UTF-8");
// 解析JSON数据并填充下拉框
try {
// 使用nlohmann库解析JSON字符串
json j = json::parse(response);
// 清空下拉框中的现有内容
m_comboBox->Clear();
// 检查是否存在 "data" 对象以及 "data.filelist" 数组
if (j.contains("data") && j["data"].is_object()) {
// 获取data对象
json dataObj = j["data"];
// 检查是否存在 "filelist" 数组
if (dataObj.contains("filelist") && dataObj["filelist"].is_array()) {
// 获取文件列表数组
json fileArray = dataObj["filelist"];
// 遍历数组中的每个文件项
for (const auto& item : fileArray) {
// 检查项是否包含name字段且为字符串类型
if (item.contains("name") && item["name"].is_string()) {
// 提取文件名
std::string name = item["name"];
// 将文件名添加到下拉框中
m_comboBox->Append(wxString::FromUTF8(name.c_str()));
}
}
}
}
}
// 捕获JSON解析错误
catch (const json::parse_error& e) {
// JSON 解析失败
std::string parseErrorMsg = "JSON解析失败: " + std::string(e.what());
wxLogError(wxString::FromUTF8(parseErrorMsg.c_str()));
}
}
4.2 示例2:格式化显示JSON数据
cpp
// 格式化JSON显示的事件处理函数
void MyFrame::OnFormatJson(wxCommandEvent& event) {
// 使用格式化方式生成JSON HTML页面
wxString html = jsonHandler.GenerateJsonHtml(jsonHandler.GetRawJsonData(), true);
// 清除WebView的历史记录
m_webView->ClearHistory();
// 重新加载页面
m_webView->Reload();
// 在WebView中显示格式化的JSON数据
m_webView->SetPage(html, "UTF-8");
}
5. 总结
通过使用nlohmann/json库和封装的JsonHandler类,我们能够:
- 简化JSON数据的解析和处理过程
- 提供格式化的JSON数据显示功能
- 实现JSON数据的保存和重用
- 保证代码的模块化和可维护性
nlohmann/json库的使用大大简化了C++中JSON数据的处理,其直观的API设计使得JSON操作就像访问原生数据结构一样简单。通过封装在JsonHandler类中,我们实现了代码的重用和更好的维护性。