简介:一套面向 Visual C++ 6.0 开发者的 JSONCPP 集成与中文解析完整案例包,解决老工程中解析含中文 JSON 容易乱码的痛点。资源基于 jsoncpp-src-0.5.0 源码,集成了 json_reader、json_writer、json_value 等核心模块,并针对 VC6.0 进行了工程配置,提供可直接编译运行的 Win32 对话框程序示例,方便在老旧 IDE 中快速上手。压缩包共 72 个文件,以 h 头文件、cpp 源文件、inl 内联实现为主体,附带 doc 使用说明文档、可执行 exe 演示程序以及少量编译中间文件,整体仅 3.77MB,体积小巧。已有590人学习下载,特别适合正在维护或迁移 VC6.0 项目、需要为传统 MFC 或控制台程序接入 JSON 读写能力的开发者。资源内含完整工程源码、防乱码的 UTF-8 处理思路、详细的使用说明文档,展示了中文字符从输入解析到界面显示的全过程,照着配置即可避免编译报错与中文乱码问题,大幅节省自行摸索时间。
1. VC6.0里跑通jsoncpp:先把中文乱码这个坎迈过去
手头还压着VC6.0工程的人,多半不是在怀旧,而是产线代码动不得。我接到过一批老MFC维护需求:界面是VC6搭的,HTTP接口返回的却是JSON数据,jsoncpp怎么都编不过去,好不容易编过了,解析中文又是一屏幕问号。这套组合的坑不在JSON本身,而在旧编译器和新库之间隔着十几年的C++标准,以及UTF-8与本地GBK编码的两套字节流。
这份jsoncpp调用全案例,我按“可编译、可解析中文、不崩溃”三条线重新整理过,适合给老工程补JSON能力的人直接照着建工程。它能让你少走我反复折腾版本选择和编码转换的那几周弯路。新工程当然可以用新编译器,但如果是动不了的存量代码,下面这套做法就是最稳的落地路径。
2. 选对jsoncpp版本并编进VC6.0:版本兼容是第一个玄学
2.1 VC6.0对jsoncpp版本的隐形门槛
VC6.0用的是上世纪90年代末的C++标准,模板、异常处理、标准库实现都不完整。jsoncpp新版本的头文件大量使用C++11特性,比如nullptr、auto、右值引用、移动构造,这些在VC6的编译器眼里全是语法错误。我最早直接拿新版本源码往工程里拖,编译报错铺天盖地,C2059、C2065、C2143轮着来,第一眼根本看不出跟JSON有什么关系。
jsoncpp从1.0.0开始明确要求C++11及以上,真正能在VC6.0下安稳编译的,只有0.6.0这个系列及更早的版本。0.6.0的代码风格还停留在老C++98时代,类设计、模板嵌套都比较克制,VC6的编译器能啃下来。判断一个jsoncpp源码包适不适合VC6,最简单的办法是打开include/json/config.h,看有没有类似#ifdef JSON_HAS_CPP_11这样按编译器版本分支的宏。新版本把这个宏的默认值设成开,老编译器没有能力定义它,但头文件里那些C++11语法不会因为一个宏就消失,该报错还是报错。
我一般会把0.6.0系列的源码包单独存一份,文件名标注“VC6专用”。项目上如果后来有人升级过jsoncpp导致编译失败,我直接让他换回这个版本,问题立刻消失。别指望靠修改头文件把新版本“打补丁兼容”到VC6,你能改到编译通过,也改不完标准库层面的不兼容,后面运行时崩溃会更难查。
2.2 把jsoncpp源码手动加入VC6工程:编译选项与链接细节
用VC6开发的人应该都用过“添加到工程”这个操作。把下载好的jsoncpp源码里的三个核心.cpp文件加进工程即可:json_reader.cpp、json_writer.cpp、json_value.cpp。头文件路径要加到工程设置的“附加包含目录”里,指向jsoncpp源码的include目录。以下是我整理的最小文件清单:
| 文件 | 作用 | 是否必须 |
|---|---|---|
| json_value.cpp | Value节点的构造、拷贝、比较、序列化基础 | 必须 |
| json_reader.cpp | 把JSON字符串解析成Value树 | 必须 |
| json_writer.cpp | 把Value树写成JSON字符串 | 必须 |
| json.h | 统一入口头文件 | 必须 |
| json/config.h | 编译分支与配置宏 | 必须 |
| json/forwards.h | Value等类型的前向声明 | 必须 |
文件加进工程后,最先遇到的是预编译头问题。VC6默认给整个工程开“使用预编译头”,但jsoncpp的.cpp文件和MFC的stdafx.h体系不搭,会在编译每个json源文件时报找不到预编译头。解决办法是针对这三个.cpp文件单独设置:右键文件,进入“设置- C/C++ - 预编译头”,改成“不使用预编译头”。这个细节我每次新建工程都会先处理,不然第一轮编译就会卡住。
接着是语言选项。jsoncpp内部有异常处理的代码路径,VC6默认的异常模型和现代编译器不完全一样,常见的做法是把jsoncpp的异常配置交给它自己的宏去控制,在工程预处理定义里加上JSONCPP_DISABLE_EXCEPTIONS,让库在解析失败时不抛异常,而是通过返回false加错误信息的方式给出反馈。这样对老代码更友好,不需要额外写try/catch。
还有一个容易忽略的字符集设置。VC6工程的“预处理器定义”里,默认可能是空或者只有WIN32,_DEBUG,_MBCS。jsoncpp本身不依赖UNICODE宏,但如果你在工程里用了MultiByteToWideChar这类Windows API做编码转换,建议保持_MBCS构建,避免工程直接切到_UNICODE后,所有CString相关的代码跟着变宽字符,引发连锁修改。MBCS字符集下,本地字符串就是GBK编码,后面第3章会把编码转换讲透。
2.3 验证环境:一个能跑的最小解析Demo
加入源码、改完设置后,先不要接业务代码,用最干净的API验证jsoncpp能不能在VC6里正常工作。下面这段代码是整个环境的“最小冒烟测试”:
// demo_smoke.cpp —— VC6下jsoncpp最小验证 #include "json/json.h" #include <stdio.h> int main() { const char* rawJson = "{\"name\":\"test\",\"count\":3}"; Json::Value root; Json::Reader reader; if (!reader.parse(rawJson, rawJson + strlen(rawJson), root)) { // 解析失败时,reader.getFormatedErrorMessages()返回错误描述 printf("parse failed: %s\n", reader.getFormatedErrorMessages().c_str()); return -1; } std::string name = root["name"].asString(); int count = root["count"].asInt(); printf("name=%s count=%d\n", name.c_str(), count); return 0; }这段代码里,reader.parse的入参用了字符串首尾指针,是jsoncpp老版本接口最稳的用法,避免传std::string时因为VC6标准库实现差异出现隐式转换问题。root["name"]返回的是Value引用,asString()把它转成std::string。printf直接输出ASCII内容,这一步只验证库本身能编译、能链接、能解析结构化数据,不牵扯中文。
我一般会在这个Demo跑通后,顺手把jsoncpp所有.cpp文件在Release和Debug两种配置下各编一次。如果Debug能编过Release编不过,十有八九是工程选项不一致,优先检查“优化”和“运行时库”两处设置。这步做完,版本兼容和工程集成就稳了,后面所有的问题只剩编码一个方向。
3. 中文防乱码完整方案:把UTF-8与GBK的转换做成强制动作
3.1 乱码的根源:两套编码在接口处错位
JSON规范规定传输编码是UTF-8,而VC6的MFC程序里,CString、char*、界面控件拿到的本地文本默认是GBK/ANSI。你从HTTP响应里收到的是UTF-8字节流,存进std::string时它只是一个字节容器,不感知编码。jsoncpp解析出来的字符串值,原样保持UTF-8编码;而printf、文件写入、CListCtrl插入,都按本地代码页GBK去解释字节。UTF-8的中文字符是三个字节一组,GBK是两字节一组,用GBK的规则去读UTF-8的数据,得到的就是“鏄煶”“娴嬭瘯”这类乱码。反方向也一样,把界面输入的GBK字符串直接塞进jsoncpp生成JSON发出去,对方按UTF-8解析,中文一样变乱码。
这个问题的本质:jsoncpp不是编码转换库,它保证“字节进、字节出”。入口给什么编码,出口就是什么编码。要让整个链路不乱码,必须在两处做显式转换:解析后把UTF-8转成GBK给界面显示;生成前把GBK转成UTF-8放进Value节点。
3.2 统一转换入口:UTF8ToGBK与GBKToUTF8两个函数
编码转换用Windows API来做,不要在VC6里引入第三方转码库,也不要用标准库的mbtowc/wcstombs,那些函数在VC6的多字节环境下行为不可控。Windows API方案是转换到宽字符中转,再转到目标编码。下面是完整的两个函数:
// enc_convert.h —— UTF-8与GBK互转 #ifndef ENC_CONVERT_H #define ENC_CONVERT_H #include <string> #include <windows.h> // UTF-8字节串 -> 本地ANSI字符串(GBK) inline std::string UTF8ToGBK(const std::string& utf8) { if (utf8.empty()) return ""; // 第一步:UTF-8 -> 宽字符(UTF-16),先拿缓冲区长度 int wLen = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), NULL, 0); if (wLen <= 0) return ""; // 第二步:分配宽字符缓冲区并做实际转换 wchar_t* wBuf = new wchar_t[wLen + 1]; MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), (int)utf8.size(), wBuf, wLen); wBuf[wLen] = L'\0'; // 第三步:宽字符 -> GBK,同样先拿目标长度 int gbkLen = WideCharToMultiByte(CP_ACP, 0, wBuf, wLen, NULL, 0, NULL, NULL); std::string gbk; if (gbkLen > 0) { char* gBuf = new char[gbkLen + 1]; WideCharToMultiByte(CP_ACP, 0, wBuf, wLen, gBuf, gbkLen, NULL, NULL); gBuf[gbkLen] = '\0'; gbk.assign(gBuf, gbkLen); delete[] gBuf; } delete[] wBuf; return gbk; } // 本地ANSI字符串(GBK) -> UTF-8字节串 inline std::string GBKToUTF8(const std::string& gbk) { if (gbk.empty()) return ""; // 第一步:GBK -> 宽字符(UTF-16) int wLen = MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), (int)gbk.size(), NULL, 0); if (wLen <= 0) return ""; wchar_t* wBuf = new wchar_t[wLen + 1]; MultiByteToWideChar(CP_ACP, 0, gbk.c_str(), (int)gbk.size(), wBuf, wLen); wBuf[wLen] = L'\0'; // 第二步:宽字符 -> UTF-8 int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wBuf, wLen, NULL, 0, NULL, NULL); std::string utf8; if (utf8Len > 0) { char* uBuf = new char[utf8Len + 1]; WideCharToMultiByte(CP_UTF8, 0, wBuf, wLen, uBuf, utf8Len, NULL, NULL); uBuf[utf8Len] = '\0'; utf8.assign(uBuf, utf8Len); delete[] uBuf; } delete[] wBuf; return utf8; } #endif这里面的核心参数有两个:第一个API调用里传入NULL,0,作用是只计算转换后需要的缓冲区长度,不实际转换;计算完长度再调用一次执行真实转换。CP_UTF8代表源编码是UTF-8,CP_ACP代表本地代码页(VC6默认环境就是GBK/936)。每次转换都走“多字节到宽字符、宽字符到多字节”两段,这段代码在VC6下没有标准库版本兼容问题,因为MultiByteToWideChar是操作系统API。
这两个函数我建议放在一个公共头文件里,任何需要处理JSON中文的模块直接包含,不要在业务代码里重复造轮子。编码转换只能做一层,如果某个函数内部又做了第二次转换,中文就变成二次乱码,这种bug最难查。
3.3 在解析与生成两侧分别封堵乱码
解析侧的标准流程是:HTTP响应体拿到std::string,先交给jsoncpp解析,取出中文字段的值,再对值做UTF8ToGBK转换,最后交给界面显示。完整代码如下:
// 解析侧:UTF-8 JSON -> 界面可读的GBK字符串 bool ParseApiResponse(const std::string& httpBody, std::string& outName) { Json::Value root; Json::Reader reader; if (!reader.parse(httpBody.data(), httpBody.data() + httpBody.size(), root)) { return false; // 或通过输出参数返回错误信息 } // root["name"]取出的值是UTF-8字节流,先转GBK再使用 std::string nameUtf8 = root["name"].asString(); std::string nameGbk = UTF8ToGBK(nameUtf8); outName = nameGbk; return true; }生成侧的方向相反:界面输入的GBK字符串,先转成UTF-8塞进Json::Value,序列化之后再发送或写入文件。代码如下:
// 生成侧:GBK界面输入 -> UTF-8 JSON请求体 std::string BuildLoginRequest(const std::string& usernameGbk, const std::string& passwordGbk) { Json::Value req; // 关键:GBK字符串必须显式转成UTF-8再放入Value req["username"] = GBKToUTF8(usernameGbk); req["password"] = GBKToUTF8(passwordGbk); req["op"] = "login"; Json::FastWriter writer; std::string body = writer.write(req); return body; // body是UTF-8字节流,可直接作为HTTP请求体发送 }判断什么时候该转、什么时候不该转,我总结成一张表贴在工程代码注释里:
| 数据所处位置 | 编码 | 操作 |
|---|---|---|
| HTTP接收缓冲区/请求体 | UTF-8 | 不做转换,直接给jsoncpp解析或序列化 |
| jsoncpp解析后的Value | UTF-8 | 取字符串值时需UTF8ToGBK |
| jsoncpp生成后的输出 | UTF-8 | 直接发送,不要额外转换 |
| 界面控件/CString/本地文件读写 | GBK | 放入Value前需GBKToUTF8 |
这条规则如果从工程一开始就定死,中文乱码基本不会出现。怕就怕有的人在某个角落直接printf了一个UTF-8字符串,看着是乱码,用了转换函数反而好了,于是以为“反正乱码就套一下转换函数”,最后数据流里转来转去,污染全链路。
4. 全案例实战:从登录鉴权到嵌套列表的JSON处理全覆盖
4.1 案例一:解析接口返回的嵌套JSON,取出中文业务字段
实际业务里,接口数据经常是嵌套结构:“data”节点里挂“userInfo”,userInfo里才是真正的用户信息。取嵌套字段时,jsoncpp支持链式下标访问,很直观。下面这段解析登录响应:
// 解析嵌套JSON:{"code":0,"data":{"userId":1001,"userInfo":{"name":"张三","dept":"研发部"}}} bool ParseLoginResponse(const std::string& httpBody) { Json::Value root; Json::Reader reader; if (!reader.parse(httpBody.data(), httpBody.data() + httpBody.size(), root)) { printf("JSON解析失败,原始数据: %s\n", httpBody.c_str()); return false; } int code = root["code"].asInt(); if (code != 0) { printf("业务错误,code=%d\n", code); return false; } // 注意:每一层下标都必须确认节点存在,否则asString()返回空 Json::Value& data = root["data"]; Json::Value& userInfo = data["userInfo"]; int userId = data["userId"].asInt(); std::string nameGbk = UTF8ToGBK(userInfo["name"].asString()); std::string deptGbk = UTF8ToGBK(userInfo["dept"].asString()); printf("用户ID=%d 姓名=%s 部门=%s\n", userId, nameGbk.c_str(), deptGbk.c_str()); return true; }这里有一个很关键的用法:Json::Value& data = root["data"],拿到的是节点的引用,不是拷贝。jsoncpp对不存在的下标会返回一个引用到内部空节点,你对它进行isValid()判断是false,但这种引用不能保存长期使用,因为下次对root的操作可能让这个引用失效。我一般只在当前作用域内使用这种引用,不做跨函数传递。
4.2 案例二:构造带中文的JSON请求体并序列化
客户端上报数据是老MFC工程里很常见的需求。界面里有几个编辑框,用户填中文,程序要把这些内容组装成JSON提交到服务端。下面这段演示完整流程:
// 组装上报数据:界面控件文本(GBK) -> UTF-8 JSON std::string BuildReportJson(const char* deviceNameGbk, const char* locationGbk, int status) { Json::Value report; report["deviceName"] = GBKToUTF8(deviceNameGbk); report["location"] = GBKToUTF8(locationGbk); report["status"] = status; report["timestamp"] = (Json::Int64)time(NULL); // 嵌套数组:告警列表 Json::Value alarmList(Json::arrayValue); for (int i = 0; i < 2; i++) { Json::Value alarm; alarm["level"] = i + 1; alarm["desc"] = GBKToUTF8(i == 0 ? "温度过高" : "压力异常"); alarmList.append(alarm); } report["alarms"] = alarmList; // StyledWriter输出带缩进,FastWriter输出单行;线上环境用FastWriter Json::FastWriter writer; return writer.write(report); }Json::Value(Json::arrayValue)是显式声明这个节点是数组类型,之后append往里加元素。如果不显式声明,第一次append时jsoncpp会自动把节点变成数组,但显式声明能让代码意图更清楚,也避免某些边界条件下类型判断出现意外。time(NULL)返回time_t,在VC6里赋值给Json::Int64需要强转,我上面直接写了(Json::Int64)强转,避免64位赋值在32位工程里的截断警告。序列化选择FastWriter还是StyledWriter,主要是看用途:调试抓包时用StyledWriter带上缩进,人眼好读;业务请求建议FastWriter,体积小。
4.3 案例三:数组遍历与节点删除:把Map逻辑换成jsoncpp写法
一个接口返回的设备列表,要遍历数组、按下标访问、按条件删除元素。jsoncpp的数组操作跟std::vector的逻辑接近,但有几个老手容易犯的错。下面这段遍历并删除:
// 遍历JSON数组,删除"disabled"字段为true的节点 void RemoveDisabledDevices(Json::Value& deviceList) { if (!deviceList.isArray()) return; // 注意:从后往前删除,避免删除后下标错位 int count = (int)deviceList.size(); for (int i = count - 1; i >= 0; i--) { Json::Value& item = deviceList[i]; if (item["disabled"].asBool()) { // removeMember()返回被删除的Value,忽略返回值即可 deviceList.removeMember(i); } } }从后往前遍历删除是数组操作的基本功:正序遍历时,删掉第i个元素后,后面的元素会整体前移,下一次递增i就会跳过一个元素。jsoncpp的removeMember(int index)在0.6.0版本里是支持的,只传一个index参数。注意这里取出的Json::Value& item只是当前元素的引用,removeMember会释放该元素内部资源,所以不要在删除之后再访问item,那是典型的悬空引用。
遍历时如果只是读数据,推荐用for + size() + 下标访问。jsoncpp自带的迭代器在VC6下类型推导有点绕,iterator、const_iterator混用时容易撞上模板实例化问题,下标访问最直接、最不容易踩坑。
4.4 案例四:非标准JSON容错:注释、失效节点与异常的边界处理
线上接口返回的JSON不一定完全符合规范。有的老服务会返回带注释的JSON(形如//注释或/注释/),有的返回空字符串,有的返回带BOM头的UTF-8文件流。jsoncpp 0.6.0的Reader内置了容错能力,默认允许注释存在,但对BOM头处理得不好,BOM的EF BB BF三个字节会被当成非法字符,导致解析失败。下面这段代码把BOM清洗掉再解析:
// 解析前预处理:去掉UTF-8 BOM头,裁剪空字符 std::string CleanRawJson(const std::string& raw) { const unsigned char* p = (const unsigned char*)raw.c_str(); // UTF-8 BOM是 EF BB BF 三个字节 if (raw.size() >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF) return raw.substr(3); return raw; } bool ParseFlexibleJson(const std::string& rawJson) { std::string cleaned = CleanRawJson(rawJson); if (cleaned.empty()) return false; Json::Value root; Json::Reader reader; // 默认允许注释;如果不希望注释被接受,改为false bool ok = reader.parse(cleaned.data(), cleaned.data() + cleaned.size(), root); if (!ok) { printf("解析异常: %s\n", reader.getFormatedErrorMessages().c_str()); return false; } // 判断关键节点是否存在,isMember比直接用下标更安全 if (!root.isMember("data")) { printf("缺少data节点\n"); return false; } return true; }reader.parse的第三个参数,也就是解析选项,在旧版本里控制是否允许注释。传true会让jsoncpp忽略//和/* */注释;传false则遇到注释直接报错。我建议解析外部接口数据时保持默认true,容错性更好。判断节点是否存在,尽量用isMember而不是直接取值的then判断,因为jsoncpp对不存在节点返回的引用是“无效节点”,asString()返回空字符串,asInt()返回0,不开isMember很容易把“字段缺失”和“字段值为空”混为一谈。
5. 避坑与排查:我在这套组合上踩过的五个典型翻车现场
5.1 坑一:编译报C2059 / C2065——新版头文件直接拍碎VC6语法解析
现象:把jsoncpp新版本源码头文件加入工程后,编译json_value.cpp,报出一大片C2059语法错误,位置全在头文件里,有的还指向std::string相关行,跟JSON毫无关系。原因:jsoncpp 1.x系列使用了C++11的移动构造、nullptr、模板别名等语法,VC6不认。解决:换回0.6.0系列源码包,这个版本的语言特性都在VC6能力范围内。我现在的固定做法是,在工程目录建一个third_party/jsoncpp_vc6/目录,源码包和头文件锁死版本,代码注释里写明“禁止升级jsoncpp”,谁升级谁负责回归测试。
5.2 坑二:链接找不到json_reader.obj符号——源文件没进工程或预编译头错位
现象:编译全过,链接时报unresolved external symbol,或者LNK2001,符号名里有json_reader相关字样。原因:源文件没有真正加入工程,或者加入工程后预编译头设置不对,json源文件被跳过编译。解决:确认json_reader.cpp、json_writer.cpp、json_value.cpp三个文件都在工程里;右键每个.cpp检查C/C++预编译头设置,改成“不使用预编译头”。链接错误里的符号名带json_reader字样时,优先检查这个,不用怀疑库本身有bug。
5.3 坑三:解析出的中文全是问号或乱码——转换函数用反了
现象:接口返回“张三”,程序的printf输出“寮犱笁”或者直接是“???”。原因:“???“大概率是UTF-8字节被当成GBK显示,或者转换时源和目标参数写反了。解决:第一步,在转换前把根字节打出来,十六进制看是不是E5 BC A0开头的UTF-8序列;第二步,确认调的是UTF8ToGBK,不是GBKToUTF8。我见过有人把两个函数封装成了一个autoConvert,意图是自动判断编码,结果在VC6里判断逻辑写得不对,中文数据被转了两遍,反而乱得更离谱。老老实实按第3章那张表分场景调用,别偷懒。
5.4 坑四:生成的JSON字符串被对方解析失败——特殊字符没转义
现象:程序生成的JSON自测没问题,但发给服务端,对方的JSON解析器报格式错误。原因:某个字符串字段里带了双引号或反斜杠,通过界面输入时被当成普通字符存进了GBK字符串,而字符串放进JSON时没有对双引号做转义。jsoncpp的序列化会自动把Value内的引号字符转成",但前提是你把原始字符串完整交给了Value。如果中间某一层手动拼接了字符串,比如用sprintf拼了半截JSON再接Value输出,转义就会断裂。解决:不要手工拼JSON,所有字段都通过Value节点赋值,再统一走writer.write()。如果坚持拼字符串,至少对内容做一次replace(" , \"),但那已经是在重造轮子。
5.5 坑五:程序运行时崩溃——Value生命周期和拷贝语义惹的祸
现象:Debug版本一切正常,Release版本偶发崩溃;或者一个函数里取了root["a"]["b"]的引用存到成员变量,下次调用就崩。原因:VC6的标准库实现里,std::string的拷贝构造和析构在不同模块(比如exe和dll)之间会因堆不同而崩溃。jsoncpp的Json::Value内部持有std::string,如果你在exe里创建Value,传到另一个dll里析构,跨模块释放内存会直接崩。另外,保存Value引用跨作用域使用,root被重新赋值或析构后,引用悬空。解决:Value对象在哪个模块创建,就在哪个模块销毁,不要做成跨DLL的接口参数;需要保存字段值时,取出来拷贝成std::string或int,不要保存引用。我后来定了一条规矩:任何函数的返回值、成员变量,都存std::string值类型,绝不存Value&。
6. 把编码细节封装成JsonHelper:老工程加JSON能力的最省事收尾
前面几章把jsoncpp的编译、中文转换、常用场景都打通了,但项目里如果到处散落着UTF8ToGBK和GBKToUTF8的调用,代码依然难维护。我习惯在最外层包一个极薄的Helper层,把“取字符串自动转GBK”“放字符串自动转UTF-8”这两个动作内聚成一个类的两个方法。
// json_helper.h —— 面向业务的最小JSON封装 class JsonHelper { public: // 从Value节点取字符串,直接返回GBK编码(界面可直接显示) static std::string GetString(const Json::Value& node, const std::string& key) { if (!node.isMember(key)) return ""; return UTF8ToGBK(node[key].asString()); } // 向Value节点放入GBK字符串,内部自动转UTF-8 static void PutString(Json::Value& node, const std::string& key, const std::string& gbkValue) { node[key] = GBKToUTF8(gbkValue); } // 对已有的Value节点恢复GBK文本 static std::string ToGBK(const Json::Value& node) { return UTF8ToGBK(node.asString()); } };这个Helper只做一件事:让业务代码里不再出现编码转换字样。取字段用GetString,放字段用PutString,底层是UTF-8还是GBK,业务层不关心。实际接入老工程时,我会把第2章的冒烟测试、第3章的转换函数、第4章的四个案例按“解析请求、生成响应、遍历数组、清洗BOM”四个场景拆成四份可编译源码,放在README里说明每个案例对应什么业务。后来接手的人如果遇到新字段,翻一遍案例基本就能自己写出来。
我自己刚折腾这套方案时,犯过最蠢的错是:工程里有两个模块都封装了编码转换函数,一个叫GB2312ToUTF8,一个叫AnsiToUTF8,底层实现一样,接口名称不同,结果两个模块互相调用对方的函数,转换链绕了一圈又回到原样。从那以后我每次给VC6工程加JSON能力,都强制把编码转换收敛到单独一个头文件里,全工程只认这一套接口,任何新代码不得另起炉灶。VC6的老工程禁不起反复试错,一次把版本、编译选项、编码方向都定死,后面加接口、调字段才不吵架。希望帮到你。
本文还有配套的精品资源,点击获取