前言
- 由于原生态的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" |
| } |
| ] |
| } |
| |
复制
| |
| 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