Asio11-Json

Asio11-Json

与protobuf区别

我们上节的protobuf可以做序列化,那么我们为什么又要介绍json格式呢?

原因是,protobuf将信息转化成了二进制的格式,虽然速度快,效率高,可读性却不好。而采用json必然会导致转换效率低,好处是可读性好,也更简单。

一般情况下,protobuf常用在后端个服务器之间通信,而json用在客户端和服务器之间的通信。

nlohmannjson 以及 jsoncpp

nlohmannjson

优点

  • 仅头文件(Header-only): 只需包含一个头文件 #include <nlohmann/json.hpp> 即可使用,无需编译库文件,极易集成。
  • 直观的语法: 其 API 设计非常人性化,与使用标准容器(如 std::map, std::vector)的体验类似。
  • 强大的类型转换: 自动在 JSON 类型和 C++ 类型之间进行转换,无需手动解析。
  • 现代 C++: 充分利用 C++11/14/17/20 的特性,如移动语义、异常等。
  • 严谨: 有严格的测试保障,代码质量高。

用法

创建json对象
#include <iostream>
#include "nlohmann/json.hpp" // 或者 <nlohmann/json.hpp>,取决于你的路径
// 为了方便,通常使用这个命名空间
using json = nlohmann::json;

// 1. 创建空对象
json j;

// 2. 使用初始化列表(最常用、最直观的方式)
json j_object = {
    {"pi", 3.141},
    {"happy", true},
    {"name", "Niels"},
    {"nothing", nullptr},
    {"answer", {
        {"everything", 42}
    }},
    {"list", {1, 0, 2}},
    {"object", {
        {"currency", "USD"},
        {"value", 42.99}
    }}
};

// 3. 像标准容器一样操作
j["new_key"] = "new_value"; // 添加字符串
j["another_list"] = { 1, 2, 3 }; // 添加数组
解析字符串
// 从字符串解析
std::string json_string = R"(
    {
        "name": "Alice",
        "age": 30,
        "courses": ["Math", "Physics"]
    }
)";

// 方法 1:使用 json::parse()
json j_from_string = json::parse(json_string);

// 方法 2:从文件解析(C++17 以上)
// #include <fstream>
std::ifstream f("example.json");
json j_from_file = json::parse(f);
生成字符串
json j = {{"name", "Bob"}, {"grades", {85, 90, 78}}};

// 生成紧凑的字符串
std::string compact_string = j.dump();
// 输出: {"grades":[85,90,78],"name":"Bob"}

// 生成格式化的字符串(美化输出,带缩进)
std::string pretty_string = j.dump(4); // 参数是缩进空格数
// 输出:
// {
//     "grades": [
//         85,
//         90,
//         78
//     ],
//     "name": "Bob"
// }

std::cout << pretty_string << std::endl;
修改
json j = {
    {"name", "Charlie"},
    {"age", 25},
    {"hobbies", {"reading", "gaming", "coding"}},
    {"address", {
        {"city", "Shanghai"},
        {"zip", "200000"}
    }}
};

// 1. 使用键访问(像 std::map)
std::string name = j["name"]; // "Charlie"
int age = j["age"]; // 25

// 2. 访问嵌套对象
std::string city = j["address"]["city"]; // "Shanghai"

// 3. 访问数组(像 std::vector)
std::string first_hobby = j["hobbies"][0]; // "reading"

// 4. 修改值
j["age"] = 26;
j["address"]["city"] = "Beijing";
j["hobbies"].push_back("hiking"); // 向数组添加新元素

// 5. 检查键是否存在
if (j.contains("address")) { // C++20 风格,推荐
    // ... 
}
// 或者使用 find()
if (j.find("address") != j.end()) {
    // ...
}

// 6. 异常处理:如果键不存在,使用 at() 会抛出异常
try {
    auto value = j.at("nonexistent_key");
} catch (json::out_of_range& e) {
    std::cout << "Key not found: " << e.what() << std::endl;
}
类型转换
// 1. 显式转换(推荐,更安全)
auto j = json::parse(R"({"number": 42, "text": "hello"})");

int number = j["number"].get<int>();
std::string text = j["text"].get<std::string>();

// 2. 隐式转换(方便,但可能不直观)
int number_implicit = j["number"];
std::string text_implicit = j["text"];

// 3. 转换整个对象为 C++ STL 容器/结构
std::vector<int> vec = j["list"].get<std::vector<int>>();
std::map<std::string, int> map = j["object"].get<std::map<std::string, int>>();
迭代
json j = {{"one", 1}, {"two", 2}, {"three", 3}};

// 迭代对象(键值对)
for (auto& [key, value] : j.items()) {
    std::cout << key << " : " << value << std::endl;
}

// 迭代数组
json j_array = {1, 2, 3, 4, 5};
for (auto& element : j_array) {
    std::cout << element << ' ';
}
自定义转换
struct Person {
    std::string name;
    int age;
    std::vector<std::string> hobbies;
};

// 告诉 nlohmann/json 如何转换你的类型
namespace nlohmann {
    template <>
    struct adl_serializer<Person> {
        static void to_json(json& j, const Person& p) {
            j = json{{"name", p.name}, {"age", p.age}, {"hobbies", p.hobbies}};
        }

        static void from_json(const json& j, Person& p) {
            j.at("name").get_to(p.name);
            j.at("age").get_to(p.age);
            j.at("hobbies").get_to(p.hobbies);
        }
    };
}

// 使用
Person alice {"Alice", 30, {"chess", "art"}};

// 自动将 Person 转换为 json
json j = alice;
std::cout << j.dump(4) << std::endl;

// 自动从 json 解析为 Person
std::string json_str = R"({"name": "Bob", "age": 25, "hobbies": ["skiing"]})";
auto j2 = json::parse(json_str);
Person bob = j2.get<Person>();

jsoncpp

优点

速度要比nlohmannjson快,同时很稳定,企业常用。

用法

简单使用
#include <iostream>
#include <json/json.h>
#include <json/value.h>
#include <json/reader.h>
int main()
{
    Json::Value root;
    root["id"] = 1001;
    root["data"] = "hello world";
    std::string request = root.toStyledString();
    std::cout << "request is " << request << std::endl;
    Json::Value root2;
    Json::Reader reader;
    reader.parse(request, root2);
    std::cout << "msg id is " << root2["id"] << " msg is " << root2["data"] << std::endl;
}
创建json对象
#include <iostream>
#include "json/json.h" // 或者 <json/json.h>
// jsoncpp 的所有内容都在 Json 命名空间下
using namespace Json;


解析 JSON 字符串(反序列化)
std::string jsonString = R"(
    {
        "name": "Alice",
        "age": 30,
        "courses": ["Math", "Physics"]
    }
)";

// 使用 CharReader 和 CharReaderBuilder
Json::CharReaderBuilder builder;
Json::CharReader* reader = builder.newCharReader();

Json::Value root; // 核心类,代表一个 JSON 值(对象、数组、字符串等)
std::string errors;

bool parsingSuccessful = reader->parse(
    jsonString.c_str(),
    jsonString.c_str() + jsonString.size(),
    &root,
    &errors
);

delete reader; // 记得释放 reader

if (!parsingSuccessful) {
    std::cout << "Failed to parse JSON: " << errors << std::endl;
    return 1;
}

// 从文件解析 (更简单,推荐)
Json::Value rootFromFile;
std::ifstream ifs("data.json");
ifs >> rootFromFile; // 使用 >> 操作符直接读入
生成 JSON 字符串(序列化)
Json::Value root;
root["name"] = "Bob";
root["age"] = 25;
root["courses"].append("Math"); // 使用 append 向数组添加元素
root["courses"].append("Physics");

// 方法 1:紧凑输出
Json::StreamWriterBuilder writerBuilder;
writerBuilder.settings_["indentation"] = ""; // 无缩进
std::string jsonOutput = Json::writeString(writerBuilder, root);
// 输出: {"age":25,"courses":["Math","Physics"],"name":"Bob"}

// 方法 2:美化输出(带缩进)
writerBuilder.settings_["indentation"] = "  "; // 两个空格缩进
std::string prettyOutput = Json::writeString(writerBuilder, root);
std::cout << prettyOutput << std::endl;

// 方法 3:直接输出到流
Json::StyledStreamWriter writer; // 一个现成的美化输出写入器
writer.write(std::cout, root);
访问和修改数据
Json::Value j;
j["name"] = "Charlie";
j["age"] = 25;
j["hobbies"].append("reading");
j["hobbies"].append("gaming");
j["address"]["city"] = "Shanghai";
j["address"]["zip"] = "200000";

// 1. 使用键访问
std::string name = j["name"].asString(); // 必须显式转换
int age = j["age"].asInt();

// 2. 访问嵌套对象
std::string city = j["address"]["city"].asString();

// 3. 访问数组
// 先检查是否是数组
if (j["hobbies"].isArray()) {
    for (int i = 0; i < j["hobbies"].size(); ++i) {
        std::cout << j["hobbies"][i].asString() << std::endl;
    }
    // 或者使用迭代器
    for (const auto& hobby : j["hobbies"]) {
        std::cout << hobby.asString() << std::endl;
    }
}

// 4. 修改值
j["age"] = 26;
j["address"]["city"] = "Beijing";

// 5. 检查键是否存在和类型
// 检查键是否存在
if (j.isMember("address")) {
    // 检查类型
    if (j["address"].isObject()) {
        // ...
    }
}
if (j["age"].isInt()) {
    // ...
}

// 6. 获取带有默认值的值
int age_with_default = j.get("age", 0).asInt(); // 如果 "age" 不存在,返回 0
std::string country = j.get("country", "China").asString(); // 默认值 "China"
类型转换方法

你必须根据 JSON 值的实际类型调用正确的 asXxx() 方法,否则会抛出异常或返回默认值。

  • asString()
  • asInt(), asUInt()
  • asInt64(), asUInt64()
  • asFloat(), asDouble()
  • asBool()

最佳实践:先检查,再获取

Json::Value value = ...;
if (value.isString()) {
    std::string s = value.asString();
} else if (value.isInt()) {
    int i = value.asInt();
} // ... 其他类型
迭代
Json::Value j = ...; // 某个 JSON 对象

// 迭代对象的键
Json::Value::Members keys = j.getMemberNames();
for (const auto& key : keys) {
    std::cout << "Key: " << key << ", Value: " << j[key] << std::endl;
}

// 或者直接迭代(C++11 风格)
for (auto it = j.begin(); it != j.end(); ++it) {
    std::cout << "Key: " << it.name() << ", Value: " << *it << std::endl;
}
posted @ 2025-12-24 23:13  大胖熊哈  阅读(8)  评论(0)    收藏  举报