作为一名本科实习生,因为在实习时需要完成c++读取并处理json文件的任务,所以在网上调研,最后选择了口碑一流,使用方便直观的nlohmann/json开源C++库,用于解析json。
也特此写一篇博客将json库的基础写出来,方便自己以后查找,还有就是分享给大家。
nlohmann 是德国工程师 Niels Lohmann,以其名字为工程名的 nlohmann/json 项目,同时也叫JSON for Modern C++。
️ 其他开发人员
Michael Hartmann
Stefan Hagen
Steve Sperandeo
Robert Jefe Lindstädt
Steve Wagner
Github上README.md也有很多详解示例和介绍,详情见链接:下载地址.
此篇主要面向未接触过 JSON 文件的新手,介绍如何快速上手使用 nlohmann 解析处理并生成 JSON 文件。
网上有无数开源的JSON解析库,每个库都有其存在的理由。个人认为使用nlohmann / json库有以下的优点:
直观的语法。类似于Python,JSON等第一类解释性语言,nlohmann/json 所有的运算符也实现了同样的感觉。详情可以看看下面的例子,你就知道我的意思了。
集成。nlohmann/json的整个代码仅由一个头文件json.hpp组成,没有其他库,没有子项目,没有依赖,没有复杂的构建系统。总之,调用起来完全不需要调整编译器或项目的设置。
模板化。每个JSON对象都有一个指针(union的最大大小)和一个枚举元素(1字节)。默认泛化是使用以下c++数据类型:
std::string表示字符串,int64_t, uint64_t或double表示数字,std::map表示对象,std::vector表示数组,bool表示布尔值。但是,可以根据自己的需要对通用类basic_json进行模板化。
处理速度。当然还有更快的JSON库,例如cjson, rapidjson。但是,通过添加一个头文件支持来加快开发速度,那么nlohmann / json这个库就是第一选择。
我是用的VScode + C++11 + Clang++来完成编译的,使用方法很简单,就是下载并添加头文件
#include "json.hpp";
using namespace nlohmann;
注意Json.hpp库需要支持 C++11的编译环境,官方提供的已知支持编译器如下:
GCC 4.8 - 11.0 (and possibly later)
Clang 3.4 - 13.0 (and possibly later)
Apple Clang 9.1 - 12.4 (and possibly later)
Intel C++ Compiler 17.0.2 (and possibly later)
Microsoft Visual C++ 2015 / Build Tools 14.0.25123.0 (and possibly later)
Microsoft Visual C++ 2017 / Build Tools 15.5.180.51428 (and possibly later)
Microsoft Visual C++ 2019 / Build Tools 16.3.1+1def00d3d (and possibly later)
操作需要适用于std::fstream或std::iostream的任何子类。
例子:
// 读取JSON文件
std::ifstream i("在此填写json格式的文件路径,例如 d:\\test.json");
json j;
i >> j;
如果遇到中文路径读取问题可以更改windows设置:
1. 通过控制面板—-日期时间语言区域—-语言选项来修改系统默认编码
2. 或者更改VScode的编码语言
3. 使用STL中的locale类的静态方法
// 将美化后的JSON写入另一个文件
std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;
Remark:
setw(4) 是用于打印格式好看的 json 文件
使用 j.dump(4) 也是一样的效果
\quad
json test; //创建一个空结构
json arr = json::array(); //表示空数组[]
json obj = json::object(); //表示空对象{}
方法一:
json j;
j["pi"] = 3.141; // 存储double型
j["happy"] = true; // 存储Boolean型
j["name"] = "Niels"; // 存储string型
j["nothing"] = nullptr; // 存储空对象
j["list"] = { 1, 0, 2 }; // 存储数组类
j["object"] = { {"currency", "USD"}, {"value", 42.99} }; // 存储对象
// 并可以在对象里嵌入对象
json inside = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"list", {1, 0, 2}},
{"object", {{"currency", "USD"},{"value", 42.99}}
};
j["object"].push_back(inside);
\quad
方法二:
// 从字符串字面量来创建对象
json j1 = "{ \"happy\": true, \"pi\": 3.141 }"_json;
// 需要注意这种方法不会解析实际的对象,而只是存储整体的字符串
或者
auto j2 = R"(
{
"happy": true,
"pi": 3.141
}
)"_json;
函数 | 作用 |
---|---|
push_back | 添加数据(构造和复制) |
emplace_back | 添加数据(仅构造) |
size | 得到json结构体的大小 |
empty | 检查json结构体是否为null |
type | 得到json结构体的种类 (object/array) |
clear | 将json结构体数据清空 |
示例:
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true); // 使用push_back创建一个数组
j.emplace_back(1.78); // 也使用emplace_back
j.size(); // 4
j.empty(); // false
j.type(); // json::value_t::array
j.clear(); // 数组再次清空
for (json::iterator it = j.begin(); it != j.end(); ++it) {
std::cout << *it << '\n';
// 或者
std::cout << it.key() << " : " << it.value() << "\n";
}
const auto tmp = j[0].get<std::string>();
j[1] = 42;
bool foo = j.at(2);
j.is_null();
j.is_boolean();
j.is_number();
j.is_object();
j.is_array();
j.is_string();
// 查找一个条目
if (o.contains("foo")) {
}
// 使用迭代查找一个条目
if (o.find("foo") != o.end()) {
}
// 简单计算特定条目的数量
int foo_present = o.count("foo");
int fob_present = o.count("fob");
// 删除一个条目
o.erase("foo");
// 创建一个json
json j_original = R"({
"num": ["one", "two", "three"],
"test": "for_test"
})"_json;
// 使用JSON指针访问成员
j_original["/baz/0"_json_pointer]; // 得到结果one
j_original["/baz/1"_json_pointer]; // 得到结果two
j_original["/test"_json_pointer]; // 得到结果for_test
// 创建一个JSON补丁
json j_patch = R"([
{ "op": "replace", "path": "/num", "value": "empty" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/test"}
])"_json;
// 应用这个补丁
json j_result = j_original.patch(j_patch);
// j_result:
// {
// "num": "empty",
// "hello": ["world"]
// }
// 比较两个json结构体,生成从参数1到参数2的转换补丁
json::diff(j_result, j_original);
// [
// { "op":" replace", "path": "/num", "value": ["one", "two", "three"] },
// { "op": "remove","path": "/hello" },
// { "op": "add", "path": "/test", "value": "for_test" }
// ]
// 创建一个类对象
struct Info_of_People{
string name;
string country;
string age;
};
// 重载json的转换函数
void to_json(json& j, const Info_of_People& p) {
j = json{
{"name", p.name},
{"country", p.country},
{"age", p.age}
};
};
void from_json(const json& j, Info_of_People& p) {
j.at("name").get_to(p.name);
j.at("country").get_to(p.country);
j.at("age").get_to(p.age);
};
// 转换示例如下:
auto j = R"([
{"name": "小明","country": China,"age": 7},
{"name": "Ton","country": American,"age": 9},
{"name": "小姜","country": China,"age": 8}
])"_json;
std::vector people = j.get<:vector>>();
nlohmann / json生成的json结构体默认是按照首字母排序,但是我需要的是按照插入顺序来存储,也能便利之后的输出。
我这里是选用一个fifo_map的头文件,下载地址
fifo_map: 一个fifo顺序的c++关联容器
fifo_map包含:
一个std::unordered_map对象来存储键顺序,
一个指向Compare的对象指针。
使用方法:
#include "json.hpp"
#include "fifo_map.hpp"
using namespace std;
using namespace nlohmann;
// 使用fifo_map作为map的解决方案
template<class Key, class Value, class dummy_compare, class trans>
using my_workaround_fifo_map = fifo_map<Key, Value, fifo_map_compare<Key>, trans>;
using my_json = basic_json<my_workaround_fifo_map>;
using Json = my_json;
//之后使用Json作为数据格式来声明就行
Json output;
output["projectname"] = "doc";
output["compiler version"] = "0.1.0";
output["showplot"] = 1;
output["displaytime"] = 1;
Json同时可作为函数来输出Json格式的参数,做一些特定修改,示例:
Json seq_Export(int cseq){
Json component_output;
component_output = {
{"type", type},
{"name", name},
{"seq", cseq}
};
return component_output;
};
注意这个库只支持UTF-8。当你在库中存储不同编码的字符串时,调用dump()可能会抛出异常,除非json::error_handler_t::replace或json::error_handler_t::ignore被用作错误处理程序。
对于中文字符的处理,可以使用更改系统的编码语言,但是这样就不具有普适性。所以我这里的处理方式,是先检测路径的的字符串是否含有中文,然后将GBK转为UTF8的格式,就能实现对中文的兼容了。
使用到的头文件和函数:
encode.cpp
#include "encode.h"
#define WINDOWS_OS __WIN32
#if WINDOWS_OS
#include
#include
std::string GBKtoUTF8(const std::string& str)
{
std::string strout = "";
WCHAR * strGBK;
int len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);
strGBK = new WCHAR[len];
MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, strGBK, len);
len = WideCharToMultiByte(CP_UTF8, 0, strGBK, -1, NULL, 0, NULL, NULL);
char * strUTF8 = new char[len];
WideCharToMultiByte(CP_UTF8, 0, strGBK, -1, strUTF8, len, NULL, NULL);
//memcpy(str, strUTF8, strlen(strUTF8));
strout = strUTF8;
delete[] strGBK;
strGBK = NULL;
delete[] strUTF8;
strUTF8 = NULL;
return strout;
};
std::string UTF8toGBK(const std::string& strint)
{
std::string strout = "";
int len = MultiByteToWideChar(CP_UTF8, 0, strint.c_str(), -1, NULL, 0);
unsigned short * wszGBK = new unsigned short[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, strint.c_str(), -1, (LPWSTR)wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, (LPWSTR)wszGBK, -1, NULL, 0, NULL, NULL);
char *szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP,0, (LPWSTR)wszGBK, -1, szGBK, len, NULL, NULL);
//strUTF8 = szGBK;
//memcpy(strout, szGBK, strlen(szGBK));
strout = szGBK;
delete[]szGBK;
delete[]wszGBK;
return strout;
};
#elif LINUX_OS_OS
#endif
encode.h
#ifndef ENCODE_H
#define ENCODE_H
#include "stdlib.h"
#include
#include
std::string GBKtoUTF8(const std::string &str);
std::string UTF8toGBK(const std::string &strint);
#endif //EMTPZ_CL_ENCODING_ISSUE_H
main.cpp
#include "encode.h"
#include "string.h"
#include "json.hpp"
#include
#include
#include
using namespace nlohmann;
using namespace std;
int IncludeChinese(char *str){
char c;
while(1){
c=*str++;
if (c==0) break; //如果到字符串尾则说明该字符串没有中文字符
if (c&0x80) //如果字符高位为1且下一字符高位也是1则有中文字符
if (*str & 0x80) return 1;
}
return 0;
}
int main(int argc, char *argv[]){
//1. read a JSON file
json jsons;
string path = argv[1];
if(IncludeChinese(path.data())){
path = GBKtoUTF8(path);
};
jsons["path"] = path;
ofstream o1("test.json");
o1 << setw(4) << jsons << endl;
return 0;
}
第一次写文章, 文章里面可能出现口水话或者错误, 但这都属于正常现象, 毕竟我也仅仅是想分享保存自己的项目经历和感受。愿读者们多多包涵,也希望可以一起交流学习!!