005-nlohmann/json 基础方法-C++开源库108杰

《二、基础方法》:节点访问、值获取、显式 vs 隐式、异常处理、迭代器、类型检测、异常处理......一节课搞定C++处理JSON数据85%的需求......

JSON 字段的简单类型包括:number、boolean、string 和 null(即空值);复杂类型则有 对象(Object)和数组(Array)两类。

1 节点与值

访问对象的指定名字的某个字段,可以使用 ["key"] ,访问指定下标的某个元素,可以使用 [ index ] 来访问(二者本质都是对 [] 操作符的重载),也可以通过方法 at(key/index) 来访问。

当指定名字(key)或下标(index)并无对应数据可供访问时,在 nlohmann/json 中,都被视为越界操作;此时,使用 [ ] 将"喜提"未定义(UB)的结果,而使用 at () 则得到异常。

通过 [] 或 at() 得到是 JSON 节点 (一个字段,或一个数组的一个元素);更多的时候,我们想要得到既定类型的值。

假设有这样一个 nlohmann/json 对象:

cpp 复制代码
nlohmann::json o = 
{
  { "id","ORD20250409-191" },
  { "customerID", 10345 },
  { "items", {123, 94320, 8} },
  { "totalAmount", 172.8 },
  { "orderDate","2025/04/09" }
};

存在隐式和显式两种取值方法,如下:

cpp 复制代码
int id1 = o["customerID"];  // 隐式
int id2;
id2 = o["customerID"]; // 隐式

int id3 = o["customerID"].get<int>(); // 显式,适用定义新变量

int id4;
o["customerID"].get_to(id4); // 显式,类型信息来自已定义的 id4

这里的显式或隐式,指的类型转换过程:JSON 节点类型到目标类型的转换过程。隐式转换会在以下两点都满足时,出现问题(造成编译失败):

  1. 目标类型重载了赋值操作符(即: = );
  2. 转换时,目标对象是已定义变量(即:确实在为某个"老"对象赋值,而非在构造新对象)。

如需进一步了解隐式转换出错的原因,建议到 d2school.com 对应课堂阅读扩展内容。

nlohmann/json官方推荐使用 get() 或 get_to (...) 显式指定要转换的目标类型。如果需要的是更严格项目管理,可以在项目中定义全局宏:JSON_USE_IMPLICIT_CONVERSIONS=0以禁用隐式取值,如是CMake项目,在CMakeList.txt 内添加代码: SET (JSON_ImplicitConversions OFF),可得相同效果。

2 迭代器

借助迭代器,有四种 for 循环可用以迭代 JSON 对象的内容(假设 o 为 某json对象):

  • 循环1
cpp 复制代码
for (auto it = o.begin(); it != o.end(); ++it)
{
    cout << it.key() << ":" << it.value() << "\n";
}
  • 循环2
cpp 复制代码
for (auto it : o.items()) // 本质同循环1
{
    cout << it.key() << ":" << it.value() << "\n";
}
  • 循环3
cpp 复制代码
for (auto v : o)  // 此时只有 value,因此比较适合遍历数组节点
{
   cout << v << "\n";
}
  • 循环4
cpp 复制代码
for (auto & [k, v] : o.items()) // 需 c++17 结构化绑定支持
{
   cout << k << ":" << v << "\n";
}

3 异常

nlohmann/json 日常操作中,有三种常见异常类型(它们的基类都是 nlohmann::json::exception)。

  • json::parse_error / 解析出错

解析的数据中,存在格式(包括编码)非法的数据,典型如:包含了非 UNICODE 编码的汉字内容。nlohmann/json 支持的UNICODE编码具体包括:UTF-8、UTF-16、UTF-32等。

注:"注释"在 JSON 标准规范中,也是一种非常格式,但因为太常见,所以 nlohmann/json 提供了支持(非默认),详见视频一(快速认识)。

  • json::out_of_range / 越界访问

使用 at(key/index) 访问数据时,查找无指定字段名或下标对应的数据时,即抛出该异常。

  • json::type_error / 类型不匹配

典型如,对一个非对象、非数组类型的JSON节点,执行 push_back(新元素) 操作。

4 视频2:基础方法

010-nlohmann/json-2-基础方法

5 示例项目-常用方法

  • 报文 demo.json
json 复制代码
{
    "name" : "丁小明",
    "age" : 12
}
  • 代码
cpp 复制代码
#include <cassert>

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    nlohmann::json o1 = 
    {
        { "id","ORD20250409-191" },
        { "customerID", 10345 },
        { "items", {123, 94320, 8} },
        { "totalAmount", 172.8 },
        { "orderDate","2025/04/09" }
    };

    std::cout << o1["id"] << std::endl;
    std::cout << o1["customerID"] << std::endl;
    std::cout << o1["items"] << std::endl;
    std::cout << o1["totalAmount"] << std::endl;
    std::cout << o1["orderDate"] << std::endl;

    auto node = o1["id"];
    std::cout << "node type-name is :\n" << typeid(node).name() << std::endl;

    // 隐式转换类型,以获取值
    {
        std::string id1 = o1["id"];
        int customerID = o1["customerID"];
        std::cout << id1 << "," << customerID << std::endl;
    }

    // 显式转换类型,以获取值
    {
        auto id2 = o1["id"].get<std::string>();
        auto customerID2 = o1["customerID"].get<int>();
        std::cout << id2 << "," << customerID2 << std::endl;
    }

    {
        double totalAmount;
        o1["totalAmount"].get_to(totalAmount);
        std::cout << totalAmount << std::endl;
        std::cout << o1["totalAmount"].get_to(totalAmount) << std::endl;        
    }

    // find、at 
    {
        json o; 
        o["name"] = "丁小明";
        o["age"] = 12;

        try
        {
            std::cout << o["Name"].get<std::string>() << " is "
                << o["age"].get<int>() <<std::endl;
        }
        catch(std::exception const& e)
        {
            std::cout << e.what() << std::endl;
        }

        auto it = o.find("Name1");
        if (it != o.end())
        {
            std::cout << it->get<std::string>() << std::endl;
        }
        else
        {
            std::cerr << "no found field : Name1." << std::endl;
        }

        try
        {
            std::cout << o.at("NAME").get<std::string>() << " is "
                << o["age"].get<int>() <<std::endl;
        }
        catch(std::exception const& e)
        {
            std::cout << e.what() << std::endl;
        }        

        std::cout << o.dump(2) << std::endl;
    }

    // 迭代器、循环
    {
        for (auto const it : o1.items())
        {            
            std::cout << it.key() << " ==> " << it.value() 
                << "\ttype : " << it.value().type_name() << std::endl;
        }

        std::cout << "==================\n";

        for (auto [k, v] : o1.items())
        {            
            std::cout << k << " ==> " << v 
                << "\ttype : " << v.type_name() << std::endl;
        }

        o1["items"].push_back(999);
        std::cout << o1["items"] << std::endl;
    }

    // 异常: 非法JSON报文
    {
        std::string s = "\"Hello JSON!------第2学堂!\"";

        try
        {
            auto j = json::parse(s);
            std::cout << j.dump() << std::endl;
        }
        catch(json::parse_error const& e)
        {
            std::cerr << e.id << "->" << e.what() << std::endl;
        }        
    }

    // 从文件读
    {
        // 请填写你的 demo.json 的实际位置
        std::ifstream ifs ("D:\\...\\CommonlyUsedJSON\\demo.json");

        if (!ifs)
        {
            std::cerr << "open file fail!" << std::endl;
            return -1;
        }

        try
        {
            std::cout << "== read from file : \n";
            auto j = json::parse(ifs);
            std::cout << j.dump(2) << std::endl;
        }
        catch(json::parse_error const& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }

    // 异常:尝试和类型不匹配的行为
    {
        using namespace nlohmann::literals;

        json j = R"(
        {
            "id" : "Hello!",
            "items": [1, 2, 3]
        }
        )"_json;

        try
        {
            j.at("items").push_back(4);
            j.at("id").push_back('a');
        }
        catch(json::type_error const& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }
}
相关推荐
Sheep Shaun34 分钟前
C++类与对象—下:夯实面向对象编程的阶梯
c语言·开发语言·数据结构·c++·算法
wuqingshun3141592 小时前
蓝桥杯 19. 植树
c++·算法·蓝桥杯·深度优先·动态规划
王禄DUT2 小时前
网络延时 第四次CCF-CSP计算机软件能力认证
c++·算法
一道秘制的小菜3 小时前
AimRT从入门到精通 - 03Channel发布者和订阅者
linux·服务器·c++·vim·aimrt
I AM_SUN4 小时前
42. 接雨水(相向双指针/前后缀分解),一篇文章讲透彻
c++·算法·leetcode
李匠20244 小时前
C++负载均衡远程调用学习之负载均衡算法与实现
运维·c++·学习·负载均衡
李匠20244 小时前
C++负载均衡远程调用学习之Agent代理模块基础构建
c++·学习
struggle20254 小时前
Sim Studio 是一个开源的代理工作流程构建器。Sim Studio 的界面是一种轻量级、直观的方式,可快速构建和部署LLMs与您最喜欢的工具连接
人工智能·开源·deepseek
梁下轻语的秋缘5 小时前
每日c/c++题 备战蓝桥杯(P1886 滑动窗口 /【模板】单调队列)
c语言·c++·蓝桥杯