ejson4cpp——一个使用极致简单且性能可比rapidjson的C++json解析库
Posted C+G
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ejson4cpp——一个使用极致简单且性能可比rapidjson的C++json解析库相关的知识,希望对你有一定的参考价值。
文章目录
代码仓库: https://github.com/ACking-you/ejson4cpp
ejson4cpp
ejosn4cpp
:意味着这是一个使用上非常 easy
,同时性能上也非常 efficiency
c++ json解析库。 支持c++11及以上,并且完全的跨平台。
-
使用
easy
体现在:- api简单,你只需要关注两个函数(FromJSON、ToJSON),且支持一键json结构体互转。
- 引入简单,支持cmake命令一键引入项目并使用。
- 错误定位简单,无论是解析json还是序列化为json,任何错误的操作都会有详细的报错信息(模拟打印了堆栈信息),让错误定位更简单。
-
性能
efficiency
体现在:
本机benchmark(3000行json)结果如图:
- 反序列化(Parse)性能明显领先
nlohmann-json
和jsoncpp
,但只有rapidjson
的一半性能。 - 序列化(Stringify)性能遥遥领先其他所有json库一个数量级。
- 查找(FindMember):由于看过
rapidjson
源码,发现其内部每个元素的节点是以数组的形式组织的,并没有用到其他高深的数据结构,故专门对他进行成员查找测试,发现确实是O(n)
级别的查找性能。
benchmark的代码仓库:https://github.com/ACking-you/bench_json4cpp
- 反序列化(Parse)性能明显领先
快速开始
要求
- C++11及以上,是跨全平台的
安装与引入
推荐用以下两种方式进行引入:
-
方法一:通过cmake中的
FetchContent
模块引入-
在项目的cmake中添加下列代码进行引入,国内如果因为网络问题无法使用可以换这个gitee的镜像源:https://gitee.com/acking-you/ejson4cpp.git
include(FetchContent) FetchContent_Declare( ejson4cpp GIT_REPOSITORY https://github.com/ACking-you/ejson4cpp.git GIT_TAG v1.5.2 GIT_SHALLOW TRUE) FetchContent_MakeAvailable(ejson4cpp)
-
在需要使用该库的目标中链接
ejson
即可。target_link_libraries(target ejson)
-
-
方法二:手动下载包,然后通过cmake命令引入
-
通过git命令下载项目源码
git clone https://github.com/ACking-you/ejson4cpp.git
-
将该项目添加到子项目中:
add_subdirectory(ejson4cpp)
-
在需要使用该库的目标中链接
ejson
即可。target_link_libraries(target ejson)
-
开始使用
这里以解析 json 的配置文件映射到 C++ 结构体为例子来进行讲解。
假设有redis、mysql、日志服务需要通过配置文件来进行配置,我们先写下结构体如下:
struct server
int port;
std::string host;
;
struct log
std::string level;
std::string filedir;
std::string formatter;
;
struct config
log logger;
server redis;
server mysql;
;
一个模拟的json配置文件如下:
"logger":
"filedir": "home/project/1",
"formatter": "default",
"level": "debug"
,
"mysql":
"host": "192.31.1.1",
"port": 1314
,
"redis":
"host": "127.0.0.1",
"port": 1444
现在要实现的功能是读取json配置文件的数据将 config 结构体进行初始化,我们可以按照下面的步骤进行:
完整代码请看 example/example1.cc
-
让server、log、config这几个自定义类型支持 json 序列化,添加下列宏定义即可:
// auto generate log/server/config to_json and from_json AUTO_GEN_NON_INTRUSIVE(log, level, filedir, formatter) AUTO_GEN_NON_INTRUSIVE(server, host, port) AUTO_GEN_NON_INTRUSIVE(config, logger, redis, mysql)
-
定义config变量,调用
FromFile
函数,即可完成需求:struct config s_config; // init config from config.json Parser::FromFile(CONFIG_PATH, s_config);
如果需要重新写回文件,则可调用
ToFile
函数:// write config to file Parser::ToFile(CONFIG_PATH, s_config);
如果读取json字符串的数据并初始化对应的变量(反序列化)则可以调用
FromJSON
函数:// init config struct from json string Parser::FromJSON(j, s_config);
如果需要将变量转化为json字符串(序列化),则可调用
ToJSON
函数:auto json_str = Parser::ToJSON(s_config);
好的,经过以上两步,你已经学会了整个库的核心用法,没错,这个库提倡使用直接的函数而不是类来实现对应的功能,这样能减少你的记忆和思考过程。当然如果需要更为细致的使用它,你可以去了解 JObject
类的相关用法,在API介绍里面写的很详细。
常见用法
在进行后端开发的过程中,前端传来的数据很多时候是 json
数据,那么我们现在就使用该库来模拟一个简单的后端业务。
比如一个视频平台的评论区,首先映入眼帘的是一条条评论,然后是发出该条评论的用户。
那么我们可以抽离出 comment
和 user_info
这两个结构体表示前端需要展示的消息,那么它在我们C++后端的请看可能是下面这样的结构体:
完整代码在 example/example2.cc
struct user_info
bool is_follow;//是否关注
int64_t id;//id信息
int64_t follow_count;//关注的博主数量
int64_t follower_count;//粉丝数量
std::string name;//用户名
;
struct comment
int64_t id;//id信息
int64_t user_id;//用户id信息
std::string created_date;//创建时间
std::string content;//评论内容
;
那么我们的后端逻辑可能会经历下面的过程:
- 从前端获取json数据(中间一般有鉴权的过程)。
- 接收json数据并其初始化为对应的C++结构体。
- 进行该次接口调用的业务逻辑处理。
- 保存数据到数据库。
那么我们用模拟数据来模拟上述过程:
-
前端的数据:
const char* comment_json = "\\n" " "content": "这是一条\\"测试\\"评论",\\n" " "created_date": "2023-01-16",\\n" " "id": 1,\\n" " "user_id": 10\\n" ""; const char* user_info_json = "\\n" " "follow_count": 12,\\n" " "follower_count": 23,\\n" " "id": 1,\\n" " "is_follow": false,\\n" " "name": "某人名字"\\n" "";
-
将数据转为C++的结构体:
需要先添加下列宏让对应的结构支持json互转AUTO_GEN_NON_INTRUSIVE(user_info, is_follow, id, follow_count, follower_count,name) AUTO_GEN_NON_INTRUSIVE(comment, id, user_id, created_date, content)
然后调用对应函数即可转化
comment cmt; user_info uinfo; Parser::FromJSON(comment_json, cmt); Parser::FromJSON(user_info_json, uinfo);
-
处理业务逻辑,这个跳过。
-
保存数据到数据库,这个模拟为保存数据到文件:
我们可以创建一个dict_t
类型的JObject
,然后把刚才的结构体以key-value
对的形式放进去,最后再调用ToFile
函数。// 4.save data to database(we simulate it to local file) auto object = JObject::Dict(); object.at("comment").get_from(cmt); object.at("user_info").get_from(uinfo); ejson::Parser::ToFile(DATA_PATH, object);
最终得到下列json数据到文件中:
"comment": "content": "这是一条"测试"评论", "created_date": "2023-01-16", "id": 1, "user_id": 10 , "user_info": "follow_count": 12, "follower_count": 23, "id": 1, "is_follow": false, "name": "某人名字"
API介绍
对所有类成员的描述的信息,可以点开 doc/html/index.html
进行查看。如果需要其他语言版本的文档,可以自己通过 Doxygen
进行生成。
通过命名风格识别API
-
所有对外暴露的静态成员函数均以
PascalCase
风格命名。如下:namespace ejson class Parser static JObject FromJSON(const str_t &content,bool skip_comment=false); template <class T> static void FromJSON(string_view const &src, T &dst,bool skip_comment=false); static JObject FromFile(string_view const &filename,bool skip_comment=false); template <class T> static void FromFile(string_view const &filename, T &dst); template <class T> static std::string ToJSON(T &&src,const int indent = -1, const char indent_char = ' ', bool is_esc = false); template <class T> static void ToFile(string_view const &filename, T const &src, const int indent = -1, const char indent_char = ' ', bool is_esc = false); static void ToFile(string_view const &filename, JObject const &src, const int indent = -1, const char indent_char = ' ', bool is_esc = false) ; class JObject static auto Dict() -> JObject; static auto List() -> JObject; ; // namespace ejson
-
所有想要暴露的普通成员函数均以
snack_case
风格命名,如下:namespace ejson class JObject auto type() const -> Type; auto at(const str_t &key) const -> ObjectRef; auto to_string(int indent = -1, char indent_char = ' ', bool is_esc = false) const -> string; void push_back(JObject item); void pop_back(); auto has_key(const str_t &key) const -> bool; template <class T> auto cast() const -> T; ; struct ObjectRef template <class T> auto get_from(T const &src) -> ObjectRef &; template <class T> void get_to(T &src); ; // namespace ejson
-
其余还有两个函数,如下:
namespace ejson_literals auto operator""_json(const char *json, size_t len) -> JObject; auto float_d(int d) -> int; // namespace ejson_literals
宏定义
利用宏定义可以方便且迅速的让自定义类型支持 FromJSON
和 ToJSON
系列函数。
实际上自定义类型在使用 FromJSON
时,只需要定义对应的 from_json
函数,使用 ToJSON
时,只需定义对应的 to_json
函数。
下列是 from_json 和 to_json 的函数签名:
void from_json(const ejson::JObject& ejson_j, T& ejson_t);
void to_json(ejson::JObject& ejson_j, const T& ejson_t);
你可以像下面这样自己实现上面这两个函数来让自定义类型支持 FromJSON
和 ToJSON
函数。
struct student
int id;
int score;
std::string name;
;
void to_json(ejson::JObject& ejson_j, const student& ejson_t)
ejson_j.at("id").get_from(ejson_t.id);
ejson_j.at("score").get_from(ejson_t.score);
ejson_j.at("name").get_from(ejson_t.name);
void from_json(const ejson::JObject& ejson_j, student& ejson_t)
ejson_j.at("id").get_to(ejson_t.id);
ejson_j.at("score").get_to(ejson_t.score);
ejson_j.at("name").get_to(ejson_t.name);
如果属性是 private
的,那么可以像下面这样侵入式的定义:
struct student
friend void to_json(ejson::JObject& ejson_j, const student& ejson_t)
ejson_j.at("id").get_from(ejson_t.id);
ejson_j.at("score").get_from(ejson_t.score);
ejson_j.at("name").get_from(ejson_t.name);
private:
int id;
int score;
std::string name;
;
FROM_JSON_FUNC&FROM_JSON_FRIEND_FUNC
用于简化 from_json
函数定义的书写,例如前面对于 strudent
类型的 from_json
函数可以这样写:
struct student
int id;
int score;
std::string name;
;
//非侵入式
FROM_JSON_FUNC(student, ejson_j, ejson_t)
ejson_j.at("id").get_to(ejson_t.id);
ejson_j.at("score").get_to(ejson_t.score);
ejson_j.at("name").get_to(ejson_t.name);
struct student
//侵入式
FROM_JSON_FRIEND_FUNC(student,ejson_j,ejson_t)
ejson_j.at("id").get_to(ejson_t.id);
ejson_j.at("score").get_to(ejson_t.score);
ejson_j.at("name").get_to(ejson_t.name);
private:
int id;
int score;
std::string name;
;
TO_JSON_FUNC&TO_JSON_FRIEND_FUNC
用于简化 to_json
函数定义的书写,例如前面对于 strudent
类型的 to_json
函数可以这样写:
struct student
int id;
int score;
std::string name;
;
//非侵入式
TO_JSON_FUNC(student, ejson_j, ejson_t)
ejson_j.at("id").get_from(ejson_t.id);
ejson_j.at("score").get_from(ejson_t.score);
ejson_j.at("name").get_from(ejson_t.name);
struct student
//侵入式
TO_JSON_FRIEND_FUNC(student,ejson_j,ejson_t)
ejson_j.at("id").get_from(ejson_t.id);
ejson_j.at("score").get_from(ejson_t.score);
ejson_j.at("name").get_from(ejson_t.name);
private:
int id;
int score;
std::string name;
;
AUTO_GEN_NON_INTRUSIVE&AUTO_GEN_INTRUSIVE
这两个宏可以帮助你一键生成之前例子中的 to_json
和 from_json
函数。
前面的代码可以替换为:
struct student
int id;
int score;
std::string name;
;
//非侵入式
AUTO_GEN_NON_INTRUSIVE(student,id,score,name)
struct student
//侵入式
AUTO_GEN_INTRUSIVE(student,id,score,name)
private:
int id;
int score;
std::string name;
;
ENABLE_JSON_COUT
自动生成对应类型的 operator<<(ostream&,T)
运算符重载,用于将对应类型支持 cout
打印出json格式。该宏可以为多个类型生成。
struct student
int id;
int score;
std::string name;
;
struct info
int id;
std::string msg;
;
//让对应类型支持json格式化
AUTO_GEN_NON_INTRUSIVE(student,id,score,name)
AUTO_GEN_NON_INTRUSIVE(info,id,msg)
//支持json格式cout打印
ENABLE_JSON_COUT(student,info)
FromJSON系列函数
参数说明
static JObject Parser::FromJSON(const str_t &content, bool skip_comment = false);
根据json字符串内容反序列化为JObject结构。
参数说明:
content
:需要解析的json资源,这是一个string_view类型的参数,支持C风格字符串和std::string
。skip_comment
:是否需要支持跳过注释,默认为false,未开启。
返回值:
- 返回解析完的 JObject 类型。
template <class T>
static void Parser::FromJSON(string_view const &src, T &dst,bool skip_comment = false)
根据json字符串内容反序列化数据到 dst
。
参数说明:
src
:需要解析的json资源,这是一个string_view类型的参数,支持C风格字符串和std::string
。dst
:需要初始化的变量,可以是自定义类型。skip_comment
:是否需要支持跳过注释,默认为false,未开启。
static JObject& Parser::FromFile(string_view const &filename, bool skip_comment = false);
根据文件中的 json 数据获取 JObject&,这个JObject是thread_local变量,也就是每个线程共用一个JObject。所以请注意,当您调用此函数时,将更新这个共用的 JObject 的值。
参数说明:
filename
:json文件路径。以上是关于ejson4cpp——一个使用极致简单且性能可比rapidjson的C++json解析库的主要内容,如果未能解决你的问题,请参考以下文章打造极致性能数据库中间件丨LVS+Keepalive+华为云DDM之理论篇