# jsonxx **Repository Path**: zh041/jsonxx ## Basic Information - **Project Name**: jsonxx - **Description**: No description available - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-09 - **Last Updated**: 2021-07-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # configor [![Open in VSCode](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/Nomango/configor) [![Github status](https://github.com/Nomango/configor/actions/workflows/unit_tests.yml/badge.svg?branch=master)](https://github.com/Nomango/configor/actions) [![codecov](https://codecov.io/gh/Nomango/configor/branch/master/graph/badge.svg?token=OO71U89I5N)](https://codecov.io/gh/Nomango/configor) [![GitHub release](https://img.shields.io/github/release/nomango/configor)](https://github.com/Nomango/configor/releases/latest) [![GitHub license](https://img.shields.io/github/license/nomango/configor)](https://github.com/Nomango/configor/blob/master/LICENSE) 一个为 C++11 量身打造的轻量级 config 库,轻松完成 JSON 解析和序列化功能,并和 C++ 输入输出流交互。 ### 功能 - 仅头文件,低接入成本 - STL-like,低学习成本 - 与标准库 io 交互 - 非侵入式的序列化与反序列化 - Unicode与多编码支持(支持`char`、`wchar_t`、`char16_t`和`char32_t`) - 可扩展的输入输出方式 > 注意:项目仍处于开发状态,可能有不兼容的修改。 ### 关于更名! 项目此前叫做 `jsonxx` 库,现已更名为 `configor`! 在保证原有 API 可用的情况下,将在未来支持包括 JSON 在内的各种常见对象存储格式(如 YAML 等)。 如果您之前已经在使用 jsonxx,那么升级到 configor 将非常简单,只需要修改头文件的声明如下: ```cpp // 替换掉注释中的头文件,改为使用下方的头文件即可 // #include "jsonxx/json.hpp" // using namespace jsonxx; #include "configor/json.hpp" using namespace configor; ``` ### 目录 - [快速入门](#快速入门) - [取值方式](#取值方式) - [常用方法和运算符](#常用方法和运算符) - 序列化与反序列化 - [序列化](#序列化) - [反序列化](#反序列化) - [Unicode与多编码支持](#Unicode与多编码支持) - [与自定义类型转换](#与自定义类型转换) - [示例代码](#示例代码) - [更多](#更多) - [计划](#计划) ### 快速上手 - 引入 configor 头文件 ```cpp #include "configor/json.hpp" using namespace configor; ``` - 使用 C++ 的方式的创建 JSON 对象 使用 `operator[]` 为 JSON 对象赋值 ```cpp json j; j["number"] = 1; j["float"] = 1.5; j["string"] = "this is a string"; j["boolean"] = true; j["user"]["id"] = 10; j["user"]["name"] = "Nomango"; ``` 使用 `std::initializer_list` 为 JSON 对象赋值 ```cpp // 使用初始化列表构造数组 json arr = { 1, 2, 3 }; // 使用初始化列表构造对象 json obj = { { "user", { { "id", 10 }, { "name", "Nomango" } } } }; // 第二个对象 json obj2 = { { "nul", nullptr }, { "number", 1 }, { "float", 1.3 }, { "boolean", false }, { "string", "中文测试" }, { "array", { 1, 2, true, 1.4 } }, { "object", { { "key", "value" }, { "key2", "value2" }, }, }; ``` 使用辅助方法构造数组或对象 ```cpp json arr = json::array({ 1 }); json obj = json::object({ { "user", { { "id", 1 }, { "name", "Nomango" } } } }); ``` ### 取值方式 - 判断 JSON 对象的值类型 ```cpp // 判断 JSON 值类型 bool is_null(); bool is_bool(); bool is_integer(); bool is_float(); bool is_number(); // is_integer() || is_float() bool is_array(); bool is_object(); ``` - JSON 对象的取值与类型转换 通过 get 函数可以直接取值: ```cpp auto b = j.get(); // bool,仅当 j.is_bool() 时可用 auto i = j.get(); // int,仅当 j.is_integer() 时可用 auto i = j.get(); // int64_t,仅当 j.is_integer() 时可用 auto f = j.get(); // float,仅当 j.is_float() 时可用 auto f = j.get(); // double,仅当 j.is_float() 时可用 auto arr = j.get(); // arr 实际是 std::vector 类型,仅当 j.is_array() 时可用 auto obj = j.get(); // obj 实际是 std::map 类型,仅当 j.is_object() 时可用 // 对于实现了 config_bind 的自定义数据类型,也可以直接取值 // 详情请参考下方 `JSON 与任意类型的转换` class MyObject; auto myObj = j.get(); ``` > 注意:get函数会强校验数据类型(例如整形和浮点数不能自动转换),参数类型与值类型不同时会引发 json_type_error 异常。 通过有参数的 get 函数,可以传入对象引用来取值: ```cpp int n = 0; j.get(n); // 取值失败时抛出 ``` 通过 try_get 函数,可以判断是否成功取值: ```cpp int n = 0; if (j.try_get(n)) { // 成功读取到 n 的值 } else { // 读取 n 值失败 } ``` 通过 as 系列函数可以将数据类型尽可能的转换: ```cpp bool as_bool(); // 对bool直接返回,对数字类型判断是否非0,对null返回false,对其他类型返回empty() int64_t as_integer(); // 对数字类型直接返回,对bool类型强转,对其他类型抛出 double as_float(); // 对数字类型直接返回,对bool类型强转,对其他类型抛出 std::string as_string(); // 对字符串类型直接返回,对数字类型和bool转换为字符串,对null返回空串,对其他类型抛出 ``` 类型转换: ```cpp // 显式转换 bool b = (bool)j["boolean"]; int i = (int)j["number"]; float d = (float)j["float"]; // 隐式转换(不推荐) bool b = j["boolean"]; int i = j["number"]; float d = j["float"]; // 对于实现了 config_bind 的自定义数据类型,也可以直接转换 // 详情请参考下方 `JSON 与任意类型的转换` class MyObject; MyObject myObj = (MyObject)j; MyObject myObj = j; ``` ### 常用方法和运算符 - size & empty & clear & count & ... ```cpp json arr = json::array({ 1, 2, 3 }); arr.size(); // 3 arr.empty(); // false arr.erase(0); // 第一个元素被删除 arr.clear(); json obj = json::object({ { "one", 1 }, { "two", 2 } }); obj.size(); // 2 arr.empty(); // false arr.count("one"); // 1 arr.count("missing"); // 0 arr.erase("one"); // one 被删除 arr.clear(); ``` - 比较运算符 ```cpp j["boolean"] == true j["number"] == 1 j["number"] != 2 j["number"] > 0 j["float"] < 3 ``` - JSON 对象类型和数组类型的遍历 ```cpp // 增强 for 循环 for (auto& j : obj) { std::cout << j << std::endl; } ``` ```cpp // 使用迭代器遍历 for (auto iter = obj.begin(); iter != obj.end(); iter++) { std::cout << iter.key() << ":" << iter.value() << std::endl; } ``` ### 序列化 - 序列化为字符串 ```cpp // 序列化为字符串 std::string json_str = j.dump(); // 美化输出,使用 4 个空格对输出进行格式化 std::string pretty_str = j.dump(4, ' '); ``` - 序列化到文件 ```cpp std::ofstream ofs("output.json"); ofs << j << std::endl; ``` ```cpp // 将 JSON 内容输出到文件,并美化 std::ofstream ofs("pretty.json"); ofs << std::setw(4) << j << std::endl; ``` - 序列化到输出流 ```cpp json j; std::cout << j; // 可以使用 std::setw(4) 对输出内容美化 ``` ### 反序列化 - 从字符串中解析 ```cpp json j = json::parse("{ \"happy\": true, \"pi\": 3.141 }"); ``` - 从文件中读取 ```cpp std::ifstream ifs("sample.json"); json j; ifs >> j; ``` - 从用户输入中读取 ```cpp json j; std::cin >> j; ``` ### Unicode与多编码支持 configor 具有完备的 unicode 支持,同时对不同平台的不同字符类型进行了支持。 对于 `wchar_t` 类型,可使用下面的别名来使用宽字符版本: ```cpp json // char wjson // wchar_t ``` 宽字符版本示例代码: ```cpp wjson j = wjson::parse(L"{ \"name\": \"中文测试\" }"); std::wstring str = j[L"name"].as_string(); // L"中文测试" ``` 对 char16_t 和 char32_t 字符类型需要使用下面的别名 ```cpp struct u16json_args : json_args { using char_type = char16_t; }; struct u32json_args : json_args { using char_type = char32_t; }; // char16_t using u16json = configor::basic_config; // char32_t using u32json = configor::basic_config; ``` > 由于C++标准库并不支持 char16_t 和 char32_t 的IO流,在不同的平台和编译器上可能会有不同表现。 > 对于 Clang 编译器来说,您可能需要自己实现 std::ctype 和 std::ctype 才能让 configor 正常工作。 ### 与自定义类型转换 - 将自定义类型与 JSON 绑定 configor 提供了 `JSON_BIND` 宏,可以用一行代码快速完成 json 绑定: ```cpp struct User { int user_id; string user_name; JSON_BIND(User, user_id, user_name); // 将 user_id 和 user_name 字段绑定到 json }; // 对私有成员变量同样适用 class User { private: int user_id; string user_name; public: JSON_BIND(User, user_id, user_name); // 将 user_id 和 user_name 字段绑定到 json }; ``` 与 JSON 绑定后,可以方便的将自定义类型与 JSON 进行转换: ```cpp json j; User user; // 将 User 转换为 json j = user; // 将 json 转换为 User user = (User)j; ``` 同时会默认支持 User 的智能指针、vector\、map\ 等类型的自动转换。 例如,下面的代码是正确的: ```cpp std::vector> user_list; json j = user_list; // 可以正确处理复合类型的转换 ``` 对于第三方库的类型,由于无法侵入式的在其内部声明 JSON_BIND,可以通过特化实现 config_bind 类,非侵入式的绑定到 JSON。 特化实现 config_bind 的例子: ```cpp // 用户类 struct User { int user_id; std::string user_name; }; // 与 json 绑定 template <> struct configor::config_bind { static void to_config(json& j, const User& v) { j = { { "user_id", v.user_id }, { "user_name", v.user_name } }; } static void from_config(const json& j, User& v) { j["user_id"].get(v.user_id); j["user_name"].get(v.user_name); } }; ``` - 将自定义类型以 JSON 格式与输入输出流交互 使用 json_wrap 函数可以让任意类型实现序列化与反序列化,并与输入输出流交互 ```cpp std::stringstream s; // 把 obj 序列化,并输入到 s 流中 s << json_wrap(obj); // 从 s 流中读取,并把 obj 反序列化 s >> json_wrap(obj); ``` ### 示例代码 1. 实现自定义User类的序列化与反序列化 ```cpp #include #include #include #include using namespace std; using namespace configor; // 用户类 struct User { int user_id; string user_name; JSON_BIND(User, user_id, user_name); }; int main(int argc, char** argv) { stringstream s("{\"user_id\": 10001, \"user_name\": \"John\"}"); // 解析json内容,并反序列化到user对象 User user; s >> json_wrap(user); // 序列化user对象并输出 cout << json_wrap(user) << endl; // {"user_id":10001,"user_name":"John"} return 0; } ``` 2. 一个HTTP接口的伪代码 ![example](./assets/example.png) ### 更多 若你需要将 JSON 解析和序列化应用到非 std::basic_stream 流中,可以通过实现自定义 `oadapter` 和 `iadapter` 的方式。 一个 oadapter 的例子: ```cpp struct myadapter : public oadapter { myadapter(std::string& str) : str_(str) { } // 实现 write 接口,写入一个字符 virtual void write(const char ch) override { str_.push_back(ch); } private: std::string& str_; }; // 使用方式 std::string output; myadapter ma{ output }; oadapterstream os{ ma }; j.dump(os); // 将 json j 序列化输出到 output 中 std::cout << output; ``` 一个 iadapter 的例子: ```cpp struct myadapter : public iadapter { myadapter(const std::string& str) : str_(str) , idx_(0) { } // 实现 read 接口,读取一个字符 virtual char read() override { if (idx_ >= str_.size()) return std::char_traits::eof(); return str_[idx_++]; } private: const std::string& str_; size_t idx_; }; // 使用方式 std::string input = "{ \"happy\": true, \"pi\": 3.141, \"name\": \"中文测试\" }"; myadapter ma{ input }; iadapterstream is{ ma }; json j = json::parse(is); // 将 input 字符串反序列化到 json ``` 详细内容请参考 json_stream.hpp ### 计划 - [x] 完全的 unicode 支持 - [x] 单测覆盖率达到 85% 以上 - [x] 支持注释 - [x] 支持 json 和自定义类型的隐式转换(has_to_json限定) - [ ] optional 返回值的支持(作为模板参数并允许替换) - [x] 错误信息完善 - [ ] SAX工具 ### 鸣谢 感谢 [nlohmann](https://github.com/nlohmann/json) 的 `JSON for Modern C++` 项目,本仓库的许多概念和灵感都来源于此。