前言
- 由于原生态的C++库并不支持JSON文件的直接读写,故本文引入JsonCpp库来辅助我们对JSON文件进行读写
JSON简介
- JSON (JavaScript Object Notation),是一种轻量级数据交换格式。
- JSON基于两种结构
- 键值对(A collection of name/value pairs)
- 值有序链表(An ordered list of values)
- 在JSON中,通常有一下几种形式:
- object:一种无序的键-值(key:value)配对。键都代表着一个JSON的字符串,而值可以是任意类型
{
"student":{
"name":"Lihua",
"age":20,
"married":false
}
}
- array:是一个值的有序链表,可以存储多种类型。
["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
JsonCpp简介
- JsonCpp是一个C++库,允许操作JSON值,包括对字符串的序列化和反序列化。它还可以在非序列化/序列化步骤中保留现有注释,使其成为存储用户输入文件的方便格式。
通过apt一键安装JsonCpp
- JsonCpp有多种下载方法,这里我们使用较为简单的软件包安装(ubuntu及其其他Debian Linux版本)
sudo apt-get install libjsoncpp-dev
- 下载后使用JsonCpp只需包含其头文件即可
#include <jsoncpp/json/json.h>
- 编译只需要添加标志位-ljsoncpp
g++ -o excutable_name source.cpp -ljsoncpp
- 在CMakeLists.txt中配置:
find_package(PkgConfig REQUIRED)
pkg_check_modules(JSONCPP jsoncpp)
include_directories(${JSONCPP_LIBRARIES})
target_link_libraries(${PROJECT_NAME} ${JSONCPP_LIBRARIES})
前置知识
Stream Classes in C++
- 概念:Stream(流)是一种电脑对输入/输出处理的代表方式。
- 而fstream提供了一系列库和方法来处理文件(file)
- ofstream:输出流(output stream),创建文件和向文件写入数据
- ifstream:输入流(input stream),从文件读取数据并且展示他们
- fstream:文件流(file stream),定义了一些文件操作类常用的流,它有上述两种流的能力
一个例子快速入门
- 创建一个alice.json文件
{
"book":"Alice in Wonderland",
"year":1865,
"characters":
[
{"name":"Jabberwock", "chapter":1},
{"name":"Cheshire Cat", "chapter":6},
{"name":"Mad Hatter", "chapter":7}
]
}
- 读取json文件
#include <iostream>
#include <fstream>
#include <jsoncpp/json/json.h>
using namespace std;
int main() {
ifstream ifs("alice.json");
Json::Reader reader;
Json::Value obj;
reader.parse(ifs, obj);
cout << "Book: " << obj["book"].asString() << endl;
cout << "Year: " << obj["year"].asUInt() << endl;
const Json::Value& characters = obj["characters"];
for (int i = 0; i < characters.size(); i++){
cout << " name: " << characters[i]["name"].asString();
cout << " chapter: " << characters[i]["chapter"].asUInt();
cout << endl;
}
}
- 输出:
Book: Alice in Wonderland
Year: 1865
name: Jabberwock chapter: 1
name: Cheshire Cat chapter: 6
name: Mad Hatter chapter: 7
JsonCpp的三大主要类别
1.Json::Value
- 说明:代表一个JSON值(value)类型
- 注意事项:
- 当你需要从JSON对象中提取原始数据(如字符串)时,应使用.asString(),.asInt()等方法,而不是直接访问成员。
- 如果尝试将错误的类型转换为数值类型,如将字符串转换为整数,将会抛出异常。
- 常用方法:
- isObject(), isArray(), isString(), isBool(), isNumber(): 这些方法用于检查Json::Value的类型。
- getObject(), getArray(), getString(), getBool(), getUInt(): 这些方法用于获取相应的类型数据,如果类型不匹配,这些方法会抛出异常。
- toStyledString(): 将JSON对象转换为格式化的字符串。
- append: 向数组类型的JSON值添加一个元素。
- put: 向对象类型的JSON值添加或修改一个成员。
- remove: 从对象中移除一个成员。
- 例子
#include <jsoncpp/json/json.h>
#include <iostream>
int main() {
Json::Value nullValue;
std::cout << nullValue.toStyledString() << std::endl;
Json::Value rootValue;
rootValue["name"] = "John Doe";
rootValue["age"] = 30;
rootValue["isStudent"] = false;
std::cout << rootValue.toStyledString() << std::endl;
std::string name = rootValue["name"].asString();
int age = rootValue["age"].asInt();
bool isStudent = rootValue["isStudent"].asBool();
std::cout << "Name: " << name << std::endl;
std::cout << "Age: " << age << std::endl;
std::cout << "Is Student: " << std::boolalpha << isStudent << std::endl;
rootValue["age"] = 31;
rootValue.remove("isStudent");
Json::ValueArray hobbies;
hobbies.append("Reading");
hobbies.append("Swimming");
rootValue["hobbies"] = hobbies;
std::cout << rootValue.toStyledString() << std::endl;
return 0;
}
- 一个特殊例子----当我们有一个嵌套例子时,我们要通过value[arraykey][index][subkey]来访问其内容
{
"media": [
{
"book"
},
{
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald"
},
{
"movie"
},
{
"music"
}
]
}
// 例如,我们要访问media数组中的第二个元素(索引为1),其title键的值
std::string title = arrayOfObjects[1]["title"].asString();
2.Json::Reader
- 说明:提供了一种强力的读取JSON文件的手段
- 注意事项
- 确保JSON字符串或文件内容是有效的,否则Json::Reader在解析时会抛出异常。
- 使用Json::Reader时,字符串字面量中的特殊字符(如引号和反斜杠)需要正确转义。例如,如果JSON中的字符串包含引号,你应该使用转义引号"。
- 常用方法:
- parse: 这是主要的解析方法,接受一个std::string或std::istream作为输入,并返回一个Json::Value对象。
- parseFromFile: 解析一个JSON文件,返回一个Json::Value对象。
- isValid: 检查JSON字符串是否有效。
- getError: 获取解析过程中的错误信息。
- 下面是一个使用Json::Reader解析JSON字符串的例子:
#include <jsoncpp/json/json.h>
#include <iostream>
#include <fstream>
int main() {
Json::Value rootValue;
std::string jsonString = "{\"name\":\"John Doe\", \"age\":30, \"isStudent\":false}";
Json::Reader reader;
bool parsingSuccessful = reader.parse(jsonString, rootValue);
if (parsingSuccessful) {
std::cout << "Name: " << rootValue["name"].asString() << std::endl;
std::cout << "Age: " << rootValue["age"].asInt() << std::endl;
std::cout << "Is Student: " << rootValue["isStudent"].asBool() << std::endl;
} else {
std::cout << "Failed to parse JSON: " << reader.getError() << std::endl;
}
return 0;
}```
- 当然我们也可以使用ifstream配合使用Json::Reader
#include <jsoncpp/json/json.h>
#include <iostream>
#include <fstream>
int main() {
Json::Value rootValue;
std::ifstream file("example.json");
if (!file.is_open()) {
std::cerr << "Error opening file" << std::endl;
return 1;
}
Json::Reader reader;
bool parsingSuccessful = reader.parse(file, rootValue, true);
file.close();
if (parsingSuccessful) {
std::cout << "Name: " << rootValue["name"].asString() << std::endl;
std::cout << "Age: " << rootValue["age"].asInt() << std::endl;
} else {
std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
}
return 0;
}
3.Json::Writer
- 注意Json::Writer是一个抽象类,其中包含了下面两个类
- FastWriter:生成未格式化的、非人类可读的文档。所有内容都将写在一行中。
- StyledWriter:生成格式化的可读文档,类似于运算符<<,但缩进较少,没有空行。
- 这里介绍FastWriter,以下是FastWriter常用的方法:
void dropNullPlaceholders();
void omitEndingLineFeed();
void enableYAMLCompatibility();
virtual std::string write(const Value &root);
- 使用例子
#include <jsoncpp/json/json.h>
#include <iostream>
int main() {
Json::Value rootValue;
rootValue["name"] = "John Doe";
rootValue["age"] = 30;
rootValue["isStudent"] = false;
Json::FastWriter writer;
std::string jsonString = writer.write(rootValue);
std::cout << jsonString << std::endl;
return 0;
}
综合使用
- 假设我们有一个巨大的数据需要通过JSON文件存储,并在另一个节点读取它
#include <iostream>
#include <sstream>
#include <fstream>
#include <jsoncpp/json/json.h>
class JSON
{
public:
void json_writer(Json::Value context,const std::string& open_file_path);
void json_reader(const std::string& open_file_path);
private:
bool is_file_empty(std::ifstream& pFile);
std::string file_name="example.json";
};
bool JSON::is_file_empty(std::ifstream& pFile)
{
return pFile.peek() == std::ifstream::traits_type::eof();
}
void JSON::json_writer(Json::Value context,const std::string& open_file_path)
{
if(context.isNull())
{
std::cout << "写入的内容是空的" << std::endl;
return ;
}
std::ifstream file_(open_file_path);
if (!is_file_empty(file_))
{
std::cout << "文件不为空,是否选择覆盖?(y/n)" << std::endl;
char userInput;
std::cin >> userInput;
if (userInput == 'y' || userInput == 'Y')
{
std::cout << "用户选择覆盖。" << std::endl;
} else if (userInput == 'n' || userInput == 'N')
{
std::cout << "用户选择不覆盖。" << std::endl;
return;
} else
{
std::cout << "无效的输入。请输入 'y' 或 'n'。" << std::endl;
return;
}
}
Json::FastWriter write;
auto str = context.toStyledString();
std::ofstream ofss;
ofss.open(open_file_path);
ofss << str;
ofss.close();
std::cout << "写入成功!" << std::endl;
}
void JSON::json_reader(const std::string& open_file_path)
{
std::ifstream ifs;
ifs.open(open_file_path);
std::stringstream buffer;
buffer << ifs.rdbuf();
ifs.close();
auto str = buffer.str();
Json::Reader reader;
Json::Value value;
if (reader.parse(str, value))
{
if (value.isArray() && value.size() > 0)
{
for (const Json::Value& coordinate : value)
{
int x = coordinate["x"].asInt();
int y = coordinate["y"].asInt();
std::cout << "Coordinate x: " << x << ", y: " << y << std::endl;
}
}else
{
std::cerr << "JSON does not contain a 'coordinates' array." << std::endl;
}
}else
{
std::cerr << "Failed to parse JSON." << std::endl;
}
}
Json::Value createLargeCoordinateArray()
{
Json::Value coordinates;
for (int i = 0; i < 100; ++i)
{
Json::Value coordinate;
coordinate["x"] = i;
coordinate["y"] = i * 2;
coordinate["linear_X"] = static_cast<int>(i * 0.1);
coordinate["angular_Z"] = static_cast<int>(i * 0.2);
coordinates.append(coordinate);
}
return coordinates;
}
int main()
{
JSON json=JSON();
Json::Value context=createLargeCoordinateArray();
std::string file_path="example3.json";
json.json_writer(context,file_path);
json.json_reader(file_path);
return 0;
}
参考文章
- https://www.json.org/json-en.html
- https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp/+/c21b4bbfdb17713af9bcd194106f69aaa289e72b/README.md
- https://www.cnblogs.com/DswCnblog/p/6678708.html
- https://en.wikibooks.org/wiki/JsonCpp#With_apt_or_apt-get
- https://www.simplilearn.com/tutorials/cpp-tutorial/ifstream-in-cpp
- https://www.daniweb.com/programming/software-development/threads/250669/check-if-file-is-empty
- https://stackoverflow.com/questions/2390912/checking-for-an-empty-file-in-c
- https://www.cnblogs.com/zipxzf/articles/14909393.html