ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VS2013下C++集成jsoncpp解析JSON文件实战指南

VS2013下C++集成jsoncpp解析JSON文件实战指南 简介这是一份针对VS2013环境下C开发者使用jsoncpp库解析JSON文件的完整示例包。资源围绕JSON解析这一常见需求演示了库文件的下载、编译与项目配置并给出可直接运行的demo适合刚接触jsoncpp或需要在老版本Visual Studio中集成JSON处理能力的开发者参考。资源包共33个文件包含头文件、源代码、工程文件、编译生成的exe与lib以及调试日志和Visual Studio配置文件等总大小3.99MB结构清晰具备开箱即用的特点。目前已有1580人学习下载结合源码与演示工程可以快速掌握jsoncpp的基本用法。通过这份资料读者既可以直接调用编译好的库文件也可以对照源码和工程配置理解在VS2013中搭建JSON解析环境的关键步骤为后续处理复杂JSON数据打下基础。1. vs2013 c jsoncpp老工程解析 JSON 文件的首选组合接手维护一个跑了好几年的 VS2013 工程对方发来一个新接口响应体全是 JSON。VS2013 的 C 标准库不提供 JSON 解析手写状态机又不现实这时最稳妥的思路就是把 jsoncpp 编进来。jsoncpp 是 C 里老牌 JSON 解析库API 风格接近 STL头文件和实现都很收敛特别适合 VS2013 这种老工具链。这篇要讲的是怎么把 jsoncpp 编译进 VS2013 工程、怎么用最小代码把 JSON 文件读成 C 对象、以及老工程里常见的字符集、运行库和工作目录这些坑在哪里。适合正在维护老代码、又必须对接 JSON 数据的 C 工程师。2. 为什么选 jsoncpp在 VS2013 环境下做 JSON 解析的选型理由2.1 JSON 在 C 里为什么难写字符串转义与嵌套结构C 里解析 JSON 的难点不在词法分析而在数据结构和字符串处理。JSON 本身只有对象、数组、字符串、数字、布尔、null 六种类型但嵌套深度不受限制一个配置文件的 JSON 可能长达十几层。用手写遍历的方式去解析字符串转义、数字边界、嵌套数组的下标管理都会变成维护负担。VS2013 时代的编译器对 C11 的支持还不完整std::regex性能差std::stoi这类转换函数对异常处理又不够灵活。更麻烦的是JSON 字符串里的中文如果混着 UTF-8 和 ANSI 编码直接按char*截取很容易把多字节字符拦腰切断。jsoncpp 把整个解析过程封装成Json::Reader加Json::Value对外暴露的是树形访问接口不需要自己管理内存和嵌套状态。另一个现实原因是VS2013 工程多是 XP/Win7 时代的老逻辑代码风格偏 C03。jsoncpp 0.x 和 1.x 系列一直保持对旧编译器的兼容不依赖 C11 的新特性在不升级工程的前提下就能编过。这个兼容性是 rapidjson 和自研解析器都比不了的。2.2 jsoncpp 与 rapidjson、cJSON 的取舍选型时最容易拿来比较的是 rapidjson 和 cJSON。rapidjson 性能确实比 jsoncpp 强解析速度快好几倍内存分配也是 SAX 和 DOM 两种模式都支持但它在 VS2013 下需要设置RAPIDJSON_NOEXCEPT之类的宏来兼容旧编译器而且异常处理逻辑在跨 DLL 时会变得复杂。cJSON 是纯 C 库整个只有两个文件适合嵌入式但接口风格是cJSON_Parse配合链表遍历拿到的每层结构都要手动释放传错指针还容易崩。jsoncpp 在这三者里最像“给应用层 C 程序员用的库”。它的Json::Value是树形结构读取时用operator[]或者get()就能取值写回时用Json::FastWriter或Json::StyledWriter序列化不需要了解 JSON 语法树的底层实现。从维护角度看jsoncpp 的头文件是json/json.h符号前缀统一编译成静态库后依赖很少。对 VS2013 这种编译器版本固定的环境jsoncpp 的 API 稳定性和老编译器兼容性比性能更重要。真到了配置文件几十 MB 或者每秒解析上千次的场景才需要换 rapidjson一般业务接口的 JSON 都是几 KB 到几百 KBjsoncpp 完全够用。2.3 jsoncpp 库的两种获取方式与版本识别获取 jsoncpp 常见做法有两种从源码库下载源码包自行编译或者拿 amalgamated 版本的合并文件。源码包适合需要改内部代码、或者要调 CMake 编译选项的场景amalgamated 版本是官方把json_reader.cpp、json_value.cpp、json_writer.cpp这几个实现文件合并到一个jsoncpp.cpp头文件也合并好直接加入工程就能编。老工程维护时我比较倾向用合并文件因为不需要引入 CMake 到现有构建流程里。拿到代码后第一件事是确认版本因为 0.x 和 1.x 的接口有差异后面的写法要跟着版本走。在工程里加一段代码能看到当前版本宏#include json/json.h #include iostream int main() { #ifdef JSONCPP_VERSION_STRING std::cout jsoncpp version: JSONCPP_VERSION_STRING std::endl; #else std::cout jsoncpp version: 0.x (pre 1.0) std::endl; #endif return 0; }这段代码通过JSONCPP_VERSION_STRING宏判断版本。jsoncpp 1.x 系列在头文件里定义了完整的版本号0.x 老版本没有这个宏所以走#else分支。知道版本后再决定用Json::Reader新接口还是Json::CharReaderBuilder。0.x 只能用Json::Reader1.8 之后Json::Reader被标记为 deprecated推荐用CharReaderBuilder但Json::Reader仍然可用。提示老工程里如果已经有一个 jsoncpp 版本在跑不要急着升到新版。升级后StyledWriter的输出格式、FastWriter的换行行为都有变化可能导致依赖 JSON 字符串格式的旧接口出问题。3. 在 VS2013 里编译并接入 jsoncpp从下载到首屏输出3.1 编译 jsoncpp 的两种方式直接编源码 vs 用 CMake 生成工程jsoncpp 接入 VS2013 工程有两条路取舍点是工程是否已经用了 CMake。如果工程本身就是 CMake 管理那直接加一行add_subdirectory(jsoncpp)或者add_library就行CMake 会生成一份适配当前 VS 版本的.vcxproj。这个过程不复杂但要注意 jsoncpp 新版 CMake 的最低版本要求老 CMake 会报错。我接触的多数 VS2013 工程是十年前用向导直接创建的.vcxproj没有 CMake。这种情况就用手动方式把源码里的json_reader.cpp、json_value.cpp、json_writer.cpp三个实现文件直接拖进工程的源文件目录然后在头文件搜索路径里加上 jsoncpp 的 include 目录。如果拿到的是 amalgamated 包那只需要加一个jsoncpp.cpp和对应的json/json.h。手动方式的好处是不动原有构建链坏处是这三个文件之间有一些内部头文件相互引用搜索路径漏配会直接报 “找不到 json_tool.h” 之类的错误。检查顺序是先确认json/json.h能找到再确认三个实现文件都在工程里最后看预处理宏。3.2 用 Json::Reader 解析一个 JSON 文件最小可运行示例在 VS2013 里新建一个控制台工程字符集选择“使用多字节字符集”下面会解释为什么把 jsoncpp 接入后直接跑这段代码#include json/json.h #include fstream #include iostream int main() { Json::Value root; Json::Reader reader; std::ifstream fin(config.json); if (!fin.is_open()) { std::cerr open config.json failed std::endl; return -1; } bool ok reader.parse(fin, root); if (!ok) { std::cerr parse error: reader.getFormattedErrorMessages() std::endl; return -1; } if (!root.isMember(server)) { std::cerr missing key: server std::endl; return -1; } std::string host root[server].get(host, 127.0.0.1).asString(); int port root[server].get(port, 8080).asInt(); std::cout host: host , port: port std::endl; return 0; }这个例子的关键在reader.parse(fin, root)。这里传的是文件流std::ifstreamjsoncpp 内部会从流里读出全部内容再解析所以调用 parse 前必须确认文件已经成功打开。getFormattedErrorMessages()返回带行列号的错误文本比如 “iterator not safe” 或者 “Missing : on object member”这是定位问题最有用的信息。root[server]返回的是一个Json::Value引用get(host, 127.0.0.1)的意思是这个键不存在时返回默认值避免直接root[server][host]因为层级缺失产生一个空 Value。这一段处理的是 JSON 转换里最典型的“反序列化”需求。注意root[server][host]这种写法在键不存在时不会抛异常而是返回一个默认的nullValue之后再调asString()只能拿到空字符串。用get()加默认值或者先isMember()判断才是安全的取值姿势。3.3 运行时配置字符集、运行时库与 JSONCPP_DLL 宏如果上面的代码编译能通过运行却报错大概率是三个和 VS2013 特性相关的配置没设对。第一字符集问题。VS2013 工程默认字符集是 Unicode所有的TCHAR字符串都会扩展成wchar_t*而std::ifstream的构造参数只接受const char*。如果工程用了 Unicode 字符集std::ifstream fin(config.json)这行会编译失败因为字符串字面量被优先转换成了宽字符。把这个示例工程切到“使用多字节字符集”是最省事的做法或者显式调用std::ifstream fin(Lconfig.json)走宽字符重载。第二运行时库。jsoncpp 静态库编译时默认会按 VS 当前的运行时库设置来编。如果 jsoncpp 编出来是/MT静态链接 VC 运行时而主工程是/MD动态链接链接时会报一堆libcpmt.lib和libcmtd.lib冲突的错误。解决办法是打开 jsoncpp 源码所在的工程把“代码生成 运行时库”改成和主工程一致。第三JSONCPP_DLL宏。如果 jsoncpp 是以 DLL 方式编译的使用方必须在预处理定义里加上JSONCPP_DLL否则编译能找到头文件链接时却报Json::Value::Value(...)找不到符号。静态库方式编译则不需要这个宏。4. jsoncpp 解析避坑5 个高频翻车点及排查4.1 解析返回 true但中文全部变成乱码现象JSON 文件里是{name: 张三}解析成功后root[name].asString()输出的是å¼ ä¸或者根本不显示。原因jsoncpp 内部把字符串按 UTF-8 处理。JSON 文件本身如果是 ANSI 编码GBKname的字节流会被 jsoncpp 直接当作 UTF-8 逐字节存入 Value取出来自然对不上。另一个常见来源是 VS2013 的源文件保存编码不是 UTF-8代码里写的中文字符串常量在编译期就被转成了 GBK 字节。解决把 JSON 文件统一转成 UTF-8 无 BOM 格式保存源文件时也选 UTF-8 with BOMVS2013 对 UTF-8 无 BOM 的源文件可能识别成 ANSI出现 C4819 警告。代码里不要直接拼 GBK 字符串到 JSON 里如果有先用MultiByteToWideChar转成 UTF-8 再赋值。4.2 程序运行时报 “open config.json failed”但文件确实在 exe 旁边现象双击运行 exe 或者按 F5 调试明明 config.json 放在 Debug 目录里程序却打不开。原因VS2013 调试时的“工作目录”默认是.vcxproj所在的工程目录不是 exe 所在目录。直接把 config.json 放在工程目录下Debug 里还留了一份旧文件程序读的是工程目录所以找不到。解决右键工程属性在“调试 工作目录”里把$(TargetDir)设成输出目录或者统一用绝对路径测试。代码层面建议先用GetModuleFileName拿到 exe 路径再拼出配置文件路径这样不管从哪里启动都能找到。4.3 parse 返回 false错误信息只有 “* Line 1, Column 2”现象getFormattedErrorMessages()输出的信息在 JSON 文件很大时定位不准只告诉你第 1 行第 2 列附近有问题但文件内容明明是从别的接口复制过来的肉眼查不出。原因这个错误位置标准往往指向第一个 token 附近真正的问题可能是一个多余的 BOM 头、文件末尾多了一个逗号、或者键名没有用双引号。jsoncpp 的错误定位能力有限遇到这种报错直接看文件头三个字节是不是EF BB BF。解决用二进制方式打开文件检查前三个字节确认有没有 BOM。然后用一个精简的 JSON 校验工具单独验证文件合法性。日常习惯是读文件时跳过 BOM或者用std::ifstream fin(path, std::ios::binary)读取预处理时把 BOM 过滤掉再进行解析。4.4root[key]取出来的 Value 判断isNull()为真但不报错现象JSON 里确实有这个键但取值后isNull()一直为 true代码没有抛异常。原因root[key]在键不存在时返回一个默认的 null Value并且不会触发错误。另一种情况是目标键的值本身是null字面量jsoncpp 也把它解析成一个null类型的 Value。解决区分“键不存在”和“值为 null”两种语义。判断键是否存在用isMember()判断值是否可读取用isNull()两者配合写才能覆盖完整逻辑。取值时优先get(key, default)在键存在但值为 null 时get也会返回默认值避免把 null 当成空字符串处理。4.5 链接时一堆 LNK2005 错误提示重复定义现象jsoncpp 加进工程后编译通过链接时报大量LNK2005 public: __thiscall Json::Value::Value(void)之类的错误而且是几十条。原因jsoncpp 源码被加入了多个编译单元或者某个头文件里直接包含了实现文件。常见的是把jsoncpp.cpp放进了工程但同时又把json_reader.cpp等三个文件也加进去了导致符号重复定义。另一种情况是 jsoncpp 编成了静态库但工程里又引入了另一个静态库两个库都打包了 jsoncpp 的符号。解决检查工程文件列表确保 jsoncpp 的实现只编一次。如果用的是 amalgamated 单文件就只保留jsoncpp.cpp不要同时加原始的拆分源文件。如果觉得一个.cpp加到全局编译太脏可以单独把 jsoncpp 编成一个远离主工程的静态库再在链接器输入里加上这个.lib。5. 用 jsoncpp 写回 JSON 与修改值两个常用场景5.1 用 StyledWriter 输出格式化 JSON 文件解析完配置文件后通常还要把修改后的配置写回文件。jsoncpp 0.x 时代的写回接口是Json::StyledWriter和Json::FastWriter前者输出带缩进、适合人读后者紧凑、适合网络传输。在 VS2013 老工程里写的代码是#include json/json.h #include fstream #include iostream int main() { Json::Value root; Json::Reader reader; std::ifstream fin(config.json); if (!fin.is_open()) { return -1; } bool ok reader.parse(fin, root); fin.close(); if (ok) { root[server][port] 9090; } else { return -1; } Json::StyledWriter writer; std::string output writer.write(root); std::ofstream fout(config_new.json); if (fout.is_open()) { fout output; fout.close(); } return 0; }理解这个写回过程要注意两点。root[server][port] 9090这一行如果server对象不存在jsoncpp 会自动创建这个层级所以数据结构是逐步构建的不要求源 JSON 里一定有这个键。writer.write(root)返回的是一个std::string文件写出时按字节流写入不会额外处理编码这就要求root里所有字符串都是 UTF-8 编码。StyledWriter默认的缩进是 3 个空格对老接口的配置文件来说这个格式基本符合预期。如果只需要压缩成一行换成Json::FastWriter即可。1.x 后这两个类也被标记为 deprecated建议用Json::StreamWriterBuilder但老工程里 0.x 版本的代码继续用老写法没有任何问题没有必要因为迁移而引入额外修改。提示写回前先确认 JSON 文件原本是不是 UTF-8 无 BOM。如果原文件是 UTF-8 with BOM用 jsoncpp 解析时 BOM 会被当成非法字符写回时也会丢掉 BOM这对某些老旧接收方程序是致命的。5.2 从 JSON 数组提取值并重组结构配置文件里经常遇到一批对象组成的数组比如多个数据库连接、多个转发规则。jsoncpp 的数组接口和 STL vector 类似按序号访问。下面这段代码演示如何从一个数据库列表里提取 name 字段并重新组装成一个只含 name 的 JSON 数组Json::Value root; Json::Reader reader; std::ifstream fin(db.json); bool ok reader.parse(fin, root); fin.close(); if (!ok) { return -1; } Json::Value nameList(Json::arrayValue); if (root.isMember(databases) root[databases].isArray()) { const Json::Value dbs root[databases]; for (Json::Value::ArrayIndex i 0; i dbs.size(); i) { if (dbs[i].isMember(name)) { nameList.append(dbs[i][name]); } } } Json::FastWriter writer; std::string output writer.write(nameList);数组遍历的写法有几个参数值得注意。Json::Value::ArrayIndex在 0.x 版本里是 unsigned int在 1.x 里也是无符号类型所以循环变量要声明成这个类型而不是int避免无符号和有符号比较的编译警告。dbs.size()返回数组长度如果dbs不是数组size() 返回 0循环不会执行不会崩溃。nameList.append(dbs[i][name])是把源 JSON 里的字符串值“拷贝”进新数组jsoncpp 的 Value 赋值默认是深拷贝所以后续修改root不会影响nameList。这是 jsoncpp 在老工程里比指针式解析方案省心的地方不会出现悬空引用。Json::arrayValue作为构造参数是告诉 Value 这个对象的初始类型是数组。如果省略nameList默认是 null 类型调用append会在内部转为数组但行为不直观建议显式声明。6. 进阶手写一个最小 JSON 解析封装省掉重复代码工程里多个模块都要读配置时每次ifstream加Reader加getFormattedErrorMessages反复粘贴容易在各处产生不一致的错误处理。我会把解析动作收拢成一个工具函数顺便兼容 0.x 和 1.x 两套接口#include json/json.h #include fstream #include string bool LoadJsonFile(const std::string path, Json::Value root, std::string err) { std::ifstream fin(path.c_str(), std::ios::binary); if (!fin.is_open()) { err open failed: path; return false; } #ifdef JSONCPP_VERSION_STRING Json::CharReaderBuilder builder; builder[collectComments] false; std::string parseErrs; bool ok Json::parseFromStream(builder, fin, root, parseErrs); if (!ok) { err parseErrs; } #else Json::Reader reader; bool ok reader.parse(fin, root); if (!ok) { err reader.getFormattedErrorMessages(); } #endif fin.close(); return ok; }这个封装把“打开文件、解析、返回错误信息”三件事收敛到一个函数里。std::ios::binary是为了防止 Windows 平台把0x1A当作 EOF 截断文件JSON 数据里一旦出现这个字节文本模式读取会提前结束导致 parse 失败。builder[collectComments] false是让 1.x 接口丢弃注释节点虽然标准 JSON 不支持注释但某些内部配置文件里会混入//开头的行这样设置后解析更宽容。调用方拿到LoadJsonFile的返回值后再针对业务字段做isMember和isNull判断。1.x 接口下错误信息在parseErrs里0.x 接口在reader.getFormattedErrorMessages()里两者都不会因为文件为空而崩溃空文件的 err 会提示 “No content”。我现在的习惯是老工程接手 jsoncpp第一件事永远是把版本固定住然后写一个类似上面的 Load 函数所有模块都走这个入口。版本一旦确认后面再排查字符集、运行时库冲突之类的问题就有明确的边界。希望这个思路帮到你少跑几趟“解析成功但值不对”的冤枉路。本文还有配套的精品资源点击获取
返回列表