Rapidjson 实战

Rapidjson 是一款 C++ 的 json 库. 支持处理 json 格式的文档. 其设计风格是头文件库, 包含头文件即可使用, 小巧轻便并且性能强悍. 本文结合样例来介绍 Rapidjson 一些常见的用法.

环境要求

有如何的几种方法可以将 Rapidjson 集成到您的项目中.

  1. Vcpkg安装: 使用 vcpkg install rapidjson即可. 如果不熟悉 vcpkg 请参考我的文章: [C++包管理工具-Vcpkg 简介]({{< relref “2024-07-29-cpp-package-management.md” >}}).

  2. CMakeFetchContent_Declare方法.

    # 引入 FetchContent 模块
    include(FetchContent)# 设置 Rapidjson 编译选项
    set(RAPIDJSON_BUILD_TESTS OFF CACHE INTERNAL "")
    set(RAPIDJSON_BUILD_DOC OFF CACHE INTERNAL "")
    set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE INTERNAL "")
    set(RAPIDJSON_BUILD_CXX20 ON CACHE INTERNAL "")FetchContent_Declare(rapidjsonURL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.zip
    )
    FetchContent_MakeAvailable(rapidjson)
    
  3. 源码安装: 下载源码并将其路径加入include目录列表中: gcc -I /path/to/rapidjson

基础用法

解析 json

auto input = R"({"name": "华安", "id": 9527})";
rapidjson::Document doc;doc.Parse(input);
if (doc.HasParseError()) {return -1;
}

访问元素

检查并获取

HasMember 查询 key 是否存在, 然后使用Is方法来判断类型是否兼容, 最后用Get方法来获取对应的值.

if (doc.HasMember("name") && doc["name"].IsString()) {std::string name = doc["name"].GetString();std::cout << "name is: " << name << std::endl;
}
if (doc.HasMember("id") && doc["id"].IsInt()) {int id = doc["id"].GetInt();std::cout << "id is: " << id << std::endl;
}
使用FindMember减少查询开销

上述示例中, doc["name"]被使用了两次, 相当于创建了两个临时变量. 使用FindMember方法则可以减少这种额外开销.

if (auto it = doc.FindMember("name");it != doc.MemberEnd() && it->value.IsString()) {std::string name = it->value.GetString();std::cout << "name is: " << name << std::endl;
}if (auto it = doc.FindMember("id");it != doc.MemberEnd() && it->value.IsInt()) {auto id = it->value.GetInt();std::cout << "id is: " << id << std::endl;
}
访问对象(Object)

查询方法与前面的基础类型相似. 需要注意的是, GetObject()方法返回的是一个const引用.
Rapidjson 为了提高效率, 接口的设计上避免使用对象拷贝.

auto response =R"({"code":200,"data":{"total":200,"curr":[12345,23456,34564]}})";rapidjson::Document doc;if (doc.Parse(response).HasParseError()) {return -1;
}if (auto it = doc.FindMember("data");it != doc.MemberEnd() && it->value.IsObject()) {const auto& data = it->value.GetObject();// ...
}
访问数组(Array)

我们用IsArray()GetArray()来判断和获取对应的数据.

需要注意的是: json 中的数组是允许多个不同类型的, 如下是一个合法的 json:

{"array": ["string", true, null, [], {}, 123]
}

但是 C++ 的数组或者容器vector仅支持相同的元素, 所以我们在获取数组元素时需要注意判断元素类型.

for (auto it = curr.Begin(); it != curr.End(); ++it) {if (it->IsInt()) {std::cout << it->GetInt() << std::endl;}
}

由于 rapidjson 支持range based for, 我们可以这样写:

for (const auto& item : curr) {if (item.IsInt()) {std::cout << item.GetInt() << std::endl;}
}

生成 json 对象

基础类型

对于基础类型(整型, 布尔值, 浮点数)我们可以直接使用AddMember添加, 需要注意的是接口中需要指定一个Allocator.

rapidjson::Document doc(rapidjson::kObjectType);
doc.AddMember("name", "华安", doc.GetAllocator());
doc.AddMember("id", 9527, doc.GetAllocator());
doc.AddMember("is_intern", true, doc.GetAllocator());

此时doc的内容为:

{ "name": "华安", "id": 9527, "is_intern": true }

为了减少对GetAllocator()的调用, 可以使用一个变量保存该结果, 见后续代码.

添加对象

一个 Object 对象可以用rapidjson::Value表示. 其添加成员的方法是AddMember(rapidjson::Documentrapidjson::Value的衍生类).

对于特殊值null, 我们可以使用SetNull()方法或者在构造函数中指定rapidjson::kNullType来实现.

rapidjson::Value contact(rapidjson::kObjectType);rapidjson::Value email;
email.SetNull();  // 设置为null
contact.AddMember("email", email, allocator);contact.AddMember("twitter", rapidjson::Value(rapidjson::kNullType),allocator);doc.AddMember("contact", contact, allocator);

此时的doc为:

{"name": "华安","id": 9527,"is_intern": true,"contact": { "email": null, "twitter": null }
}

添加数组

Array 类型的创建和添加如下所示.

auto& allocator = doc.GetAllocator();rapidjson::Value friends(rapidjson::kArrayType);
friends.PushBack("祝枝山", allocator);
friends.PushBack("文征明", allocator);
friends.PushBack("徐祯卿", allocator);doc.AddMember("friends", friends, allocator);

此时doc为:

{"name": "华安","id": 9527,"is_intern": true,"contact": { "email": null, "twitter": null },"friends": ["祝枝山", "文征明", "徐祯卿"]
}

序列化 json 对象

#include <rapidjson/document.h>
#include <rapidjson/filewritestream.h>
#include <rapidjson/writer.h>#include <iostream>void print(rapidjson::Value& value) {rapidjson::StringBuffer buffer;rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);value.Accept(writer);std::cout << buffer.GetString() << std::endl;
}

进阶用法

使用函数模板简化解析

从前面解析的例子我们可以看到, 对每一个字段都要解析代码, 这样会存在很多的代码冗余.

可以通过模板函数来实现一个解析代码. 我们用std::variant来存储不同的解析类型, 比如:int*, double*, std::string*等.

接着我们用std::visit来访问std::variant, 针对不同类型做不同的解析, 对目前尚不支持的类型则报错.

template <typename... T>
bool Parse(rapidjson::Value& data, const char* name,std::variant<T...>& target) {auto it = data.FindMember(name);if (it == data.MemberEnd()) {std::cerr << "key not found: " << name << std::endl;return false;  // 字段不存在}// 使用 std::visit 处理 std::variantreturn std::visit([&](auto value) {using ValueType = std::remove_pointer_t<decltype(value)>;if constexpr (std::is_same_v<ValueType, std::string>) {if (!it->value.IsString()) {std::cerr << "string not match: " << name << std::endl;return false;  // 类型不匹配}*value = std::string(it->value.GetString(), it->value.GetStringLength());} else if constexpr (std::is_integral_v<ValueType> ||std::is_floating_point_v<ValueType>) {if (!it->value.Is<ValueType>()) {std::cerr << "integer not found: " << name << std::endl;return false;  // 类型不匹配}*value = it->value.Get<ValueType>();} else {std::cerr << "unsupported type\n";return false;  // 不支持的类型}return true;  // 解析成功},target);
}

如何使用呢? 此处以解析一个结构体为例:

struct Person {bool married = false;int id = 0;int age = 0;double point = 0;std::string name;std::string email;
};bool ParsePerson(Person* person, rapidjson::Value& json) {std::vector<std::tuple<const char*, std::variant<int*, std::string*, double*, bool*>>>list = {{"id", &person->id},           {"age", &person->age},{"name", &person->name},       {"email", &person->email},{"married", &person->married}, {"point", &person->point},};for (auto& [name, variant] : list) {if (!Parse(json, name, variant)) {return false;  // 解析失败}}return true;  // 解析成功
}

完整示例请参考仓库代码: parse.cpp

处理多重嵌套

在工作中我们有时候会遇到嵌套很深的 json 文档. 比如给定这样一个 json 文档, 现在我们要获取/data/avatar/image/thumbnail如何操作?

{"code": 200,"data": {"avatar": {"image": {"medium": "https://image.com/hua.an.jpg","thumbnail": "https://image.com/hua.an-thumbnail.jpg"}}}
}

如果按照之前的写法层层解析, 那么必然是个很深的嵌套. 但是现在有个更好的解决办法, 就是JSONPath, 在 rapidjson 里面就是 Pointer类, 参考如下写法:

rapidjson::Document doc;if (doc.Parse(response).HasParseError()) {std::cerr << "JSON parse error!" << std::endl;return -1;
}// 使用 RapidJSON 的 Pointer 解析 JSONPath
const char* jsonpath = "/data/avatar/image/thumbnail";
rapidjson::Pointer pointer(jsonpath);// 获取 JSONPath 对应的值
if (rapidjson::Value* value = pointer.Get(doc)) {if (value->IsString()) {std::cout << "Thumbnail URL: " << value->GetString() << std::endl;} else {std::cerr << "Thumbnail is not a string!" << std::endl;}
} else {std::cerr << "Thumbnail not found!" << std::endl;
}

完整的代码请参考: jsonpath.cpp

总结

本文通过示例介绍了一些 rapidjson 的使用方法, 包括解析,生成,以及如何做代码优化. 希望能给读者带来一些帮助.

如果您觉得有用, 希望您点赞收藏关注, 感激不尽.

源码链接

  • 源码链接
  • Rapidjson 官网
  • Rapidjson Pointer

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/13055.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

北大AGI与具身智能评估新范式!Tong测试:基于动态具身物理和社会互动的评估标准

作者&#xff1a;Yujia Peng, Jiaheng Han, Zhenliang Zhang, Lifeng Fan, Tengyu Liu, Siyuan Qi, Xue Feng, Yuxi Ma, Yizhou Wang, Song-Chun Zhu 单位&#xff1a;北京通用人工智能研究院国家通用人工智能重点实验室&#xff0c;北京大学人工智能研究所&#xff0c;北京大…

开发板上Qt运行的环境变量的三条设置语句的详解

在终端中运行下面三句命令用于配置开发板上Qt运行的环境变量&#xff1a; export QT_QPA_GENERIC_PLUGINStslib:/dev/input/event1 export QT_QPA_PLATFORMlinuxfb:fb/dev/fb0 export QT_QPA_FONTDIR/usr/lib/fonts/设置成功后可以用下面的语句检查设置成功没有 echo $QT_QPA…

一文讲解Spring如何解决循环依赖

Spring 通过三级缓存机制来解决循环依赖&#xff1a; 一级缓存&#xff1a;存放完全初始化好的单例 Bean。 二级缓存&#xff1a;存放正在创建但未完全初始化的 Bean 实例。 三级缓存&#xff1a;存放 Bean 工厂对象&#xff0c;用于提前暴露 Bean。 试问:三级缓存解决循环依…

Linux+Docer 容器化部署之 Shell 语法入门篇 【Shell 替代】

&#x1f380;&#x1f380;Shell语法入门篇 系列篇 &#x1f380;&#x1f380; LinuxDocer 容器化部署之 Shell 语法入门篇 【准备阶段】LinuxDocer 容器化部署之 Shell 语法入门篇 【Shell变量】LinuxDocer 容器化部署之 Shell 语法入门篇 【Shell数组与函数】LinuxDocer 容…

[c语言日寄]赋值操作对内存的影响

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

HTML5 教程之标签(3)

HTML5 <center> 标签 (已废弃) 定义和用法 <center> 标签对其包围的文本进行水平居中处理。HTML5不支持使用<center>标签&#xff0c;因此有关该标签的更多信息&#xff0c;请参考“HTML <center>标签”部分&#xff01; 示例: <center>这个…

SQL 秒变 ER 图 sql转er图

&#x1f680;SQL 秒变 ER 图&#xff0c;校园小助手神了&#xff01; 学数据库的宝子们集合&#x1f64b;‍♀️ 是不是每次碰到 SQL 转 ER 图就头皮发麻&#xff1f;看着密密麻麻的代码&#xff0c;脑子直接死机&#xff0c;好不容易理清一点头绪&#xff0c;又被复杂的表关…

大语言模型轻量化:知识蒸馏的范式迁移与工程实践

大语言模型轻量化&#xff1a;知识蒸馏的范式迁移与工程实践 &#x1f31f; 嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 摘要 在大型语言模型&#xff…

RabbitMQ:python基础调用

前言 紧接上回在windows上安装了最新版的RabbitMQ&#xff1a; RabbitMQ&#xff1a;windows最新版本4.0.5安装方案-CSDN博客 这是官方给出的使用文档&#xff1a;How to Use RabbitMQ | RabbitMQ 这里我给出通过AI学习到的python使用方法 理论截图 python直接使用pip安装pi…

【多线程】线程池核心数到底如何配置?

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 前置回顾2. 动态线程池2.1 JMX 的介绍2.1.1 MBeans 介绍 2.2 使用 JMX jconsole 实现动态修改线程池2.2.…

【LeetCode】5. 贪心算法:买卖股票时机

太久没更了&#xff0c;抽空学习下。 看一道简单题。 class Solution:def maxProfit(self, prices: List[int]) -> int:cost -1profit 0for i in prices:if cost -1:cost icontinueprofit_ i - costif profit_ > profit:profit profit_if cost > i:cost iret…

蓝桥杯思维训练营(三)

文章目录 题目详解680.验证回文串 II30.魔塔游戏徒步旅行中的补给问题观光景点组合得分问题 题目详解 680.验证回文串 II 680.验证回文串 II 思路分析&#xff1a;这个题目的关键就是&#xff0c;按照正常来判断对应位置是否相等&#xff0c;如果不相等&#xff0c;那么就判…

DeepSeek大模型介绍、本地化部署与使用!【AI大模型】

一、DeepSeek 是什么&#xff1f; 1.技术定位 专注大模型与AGI研究&#xff0c;开发高性能基座模型&#xff08;如 DeepSeek LLM 系列&#xff09;&#xff0c;支持长文本、多模态、代码生成等复杂任务。 提供开源模型&#xff08;如 DeepSeek-MoE、DeepSeek-V2&#xff09;…

YK人工智能(六)——万字长文学会基于Torch模型网络可视化

1. 可视化网络结构 随着深度神经网络做的的发展&#xff0c;网络的结构越来越复杂&#xff0c;我们也很难确定每一层的输入结构&#xff0c;输出结构以及参数等信息&#xff0c;这样导致我们很难在短时间内完成debug。因此掌握一个可以用来可视化网络结构的工具是十分有必要的…

React+AI 技术栈(2025 版)

文章目录 核心&#xff1a;React TypeScript元框架&#xff1a;Next.js样式设计&#xff1a;Tailwind CSSshadcn/ui客户端状态管理&#xff1a;Zustand服务器状态管理&#xff1a;TanStack Query动画效果&#xff1a;Motion测试工具表格处理&#xff1a;TanStack Table表单处理…

控件【QT】

文章目录 控件QWidgetenabledgeometrysetGeometry qrcwindowOpacityQPixmapfonttoolTipfocusPolicystyleSheetQPushButtonRadio ButtionCheck Box显示类控件QProgressBarcalendarWidget 控件 Qt中已经提供了很多内置的控件了(按钮,文本框,单选按钮,复选按钮&#xff0c;下拉框…

苹果再度砍掉AR眼镜项目?AR真的是伪风口吗?

曾经&#xff0c;AR游戏一度异常火热&#xff0c;宝可梦go让多少人不惜翻墙都要去玩&#xff0c;但是也没过去几年&#xff0c;苹果被曝出再度砍掉了AR眼镜项目&#xff0c;面对着市场的变化&#xff0c;让人不禁想问AR真的是伪风口吗&#xff1f; 一、苹果再度砍掉AR眼镜项目&…

《redis4.0 通信模块源码分析(一)》

【redis导读】redis作为一款高性能的内存数据库&#xff0c;面试服务端开发&#xff0c;redis是绕不开的话题&#xff0c;如果想提升自己的网络编程的水平和技巧&#xff0c;redis这款优秀的开源软件是很值得大家去分析和研究的。 笔者从大学毕业一直有分析redis源码的想法&…

日期选择控件,时间跨度最大一年。

<el-date-picker v-model"times" type"daterange" unlink-panels :picker-options"pickerOptions" :range-separator"$lang(至)":start-placeholder"$lang(开始)" :end-placeholder"$lang(结束)" :default-tim…

JDK9新特性

文章目录 新特性&#xff1a;1.模块化系统使用模块化module-info.java&#xff1a;exports&#xff1a;opens&#xff1a;requires&#xff1a;provides&#xff1a;uses&#xff1a; 2.JShell启动Jshell执行计算定义变量定义方法定义类帮助命令查看定义的变量&#xff1a;/var…