什么是Json
Json数据格式说明:https://www.json.org/json-en.html
Json(JavaScript Object Notation )是一种轻量级的数据交换格式。简而言之,Json组织形式就和python中的字典, C/C++中的map一样,是通过key-value对来组织的,key是任意一个唯一字符串,value可以是bool,int,string 或者嵌套的一个json。关于Json 格式可以参考官方网站。
Jsoncpp处理Json
是一个用来处理 Json文本的开源C++库。源码下载地址:http://sourceforge.net/projects/jsoncpp/
优势
易用易读,结构清晰
性能
Jsoncpp目前在项目上使用,出现解析速度太慢的问题,然后库属性C/C++页面,点击优化选项卡,启动最大速度优化,速度快了一倍,但还是由于数据超过了4Mb,导致解析耗时100秒
通过更新库为cJson,达到了性能上的要求,项目上只需要添加cjson源码:cJson.h和cJson.cpp,包含头文件cJson.h就能使用。但是需要手动释放内存
网上的资料显示yyjson目前可以说是最快的解析json神器,以后如果还需要更快的解析速度,将尝试使用
一个cJson简单使用的例子
前言
cJson使用
cJSON*
cjson_test = NULL; //声明一个cjson对象
cjson_test
= cJSON_CreateObject(); //创建一个JSON数据对象(链表头结点)
cJSON_AddStringToObject(cjson_test,
"name",
"mculover666");
//添加一条字符串类型的JSON数据(添加一个链表节点)
cJSON_AddNumberToObject(cjson_test,
"age",
22); //添加一条整数类型的JSON数据(添加一个链表节点)
cJSON_AddNumberToObject(cjson_test,
"weight",
55.5); //添加一条浮点类型的JSON数据(添加一个链表节点)
cJSON_AddFalseToObject(cjson_test,
"student");
//添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点)
cjson_address = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_address,
"country",
"China");
cJSON_AddNumberToObject(cjson_address,
"zip-code",
111111);
cJSON_AddItemToObject(cjson_test,
"address",
cjson_address);
//添加一个嵌套的JSON数据(添加一个链表节点)
{
"address":
{
"country":
"China",
"zip-code":
111111
}
}
cjson_skill = cJSON_CreateArray();
cJSON_AddItemToArray(cjson_skill,
cJSON_CreateString("C"));
cJSON_AddItemToArray(cjson_skill,
cJSON_CreateString("Java"));
cJSON_AddItemToArray(cjson_skill,
cJSON_CreateString("Python"));
cJSON_AddItemToObject(cjson_test,
"skill",
cjson_skill);
//添加一个数组类型的JSON数据(添加一个链表节点)
{
"skill":
[
"C",
"Java",
"Python"
]
}
std::string str
= cJSON_Print(cjson_test); //打印JSON对象(整条链表)的所有数据
cjson_test
= cJSON_Parse(str .c_str()); //将string转换成json
cJSON_Print() 和
cJSON_CreateObject()、cJSON_Parse();都需要释放,否则会导致内存泄露
cJSON_Delete(cjson_test);
Linux环境构建动态库
新版本Linux编译指令
mkdir -p build/debug
cd build/debug
cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=ON -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../..
make
动态库存放地址jsoncpp-master/build/debug/src/lib_json
jsoncpp0.5.0版本采用scons进行编译
wget https://sourceforge.net/projects/jsoncpp/files/jsoncpp/0.5.0/jsoncpp-src-0.5.0.tar.gz/download
进入解压文件夹
scons platform=linux-gcc
root@localhost jsoncpp-src-0.5.0]# cd libs/
[root@localhost libs]# ls
linux-gcc-4.8.5
[root@localhost libs]# cd linux-gcc-4.8.5/
[root@localhost linux-gcc-4.8.5]# ls
libjson_linux-gcc-4.8.5_libmt.a libjson_linux-gcc-4.8.5_libmt.so
注意:
编译测试代码出错
In file included from /home/jsoncpp-master/src/test_lib_json/jsontest.cpp:7:0:
/home/jsoncpp-master/src/test_lib_json/jsontest.h: 在成员函数‘JsonTest::TestResult& JsonTest::TestResult::operator<<(const T&)’中:
/homjsoncpp-master/src/test_lib_json/jsontest.h:87:37: 错误:‘hexfloat’不是‘std’的成员
oss << std::setprecision(16) << std::hexfloat << value;
^
make[2]: *** [src/test_lib_json/CMakeFiles/jsoncpp_test.dir/jsontest.cpp.o] 错误 1
make[1]: *** [src/test_lib_json/CMakeFiles/jsoncpp_test.dir/all] 错误 2
说明当前的编译器不支持hexfloat语言特性,这是C11最新版本的特性,需要升级编译器
数据类型判断
不使用严格Json格式,当遇到非法Json时,自动容错为字符串,取数组下标时会直接终止进程 。使用严格Json格式,遇到非法Json时,会读取失败,但程序不会崩掉
常见的崩溃日志转存文件dmp,本地调试提示:
0x751AC602 处有未经处理的异常: Microsoft C++ 异常: Json::LogicError,位于内存位置 0x1AD4E524 处。
根据 Json::LogicError就可以知道是Json类型读取有问题,通过排查Json代码的调用方式就可以解决
类型判断
jsoncpp判断某个字段是否存在的方法如下:
1)if(root["url"].type() != Json::nullValue)
2)if(value["sex"].isNull())或者if(value.isMember(“sex”))
如果想读取某一个字段,但是又不知道字段是否存在,可以直接这样子使用
if(value["sex"].isString)
{ //do something} //不需要先判断是否isNULL
数组存在判断
如果传递的json字符串,无法确切得知是一个简单的json字符串,或者是一个数组json字符串,如果采用如下的方式判断键值是否存在,会导致程序崩溃
例如
[{“sex”:”male”}] 或者{“sex”:”male”}
if(value["sex"].isNull())或者if(value.isMember(“sex”)) 会导致崩溃,所以应该先判断是否是一个数组,然后才调用相应的判断
if(value. isArray()
{
}
不同数据类型的读取
基本数据类型的读取
{
"name": "fengyuzaitu",
"age": 29.5,
"married": false,
"utcsecond": 1548745708,
"utcmilisecond": 1548745708000
}
std::ifstream ifs;
ifs.open("testdata.json", std::ios::binary);
Json::Reader reader(Json::Features::strictMode());
Json::Value root;
if (NULL == reader.parse(ifs, root))
{
ifs.close();
return -1;
}
ifs.close();
if (!root["name"].isString()) return -1;
if (!root["age"].isDouble()) return -1;
if (!root["married"].isBool()) return -1;
if (!root["utcsecond"].isInt()) return -1;
if (!root["utcmilisecond"].isInt64()) return -1;
std::string strName = root["name"].asString();
double dAge = root["age"].asDouble();
bool bMarried = root["married"].asBool();
std::int32_t nUtcSecond = root["utcsecond"].asInt();
std::int64_t llUtcMiliSecond = root["utcmilisecond"].asInt64();
字符串数组
{
"success": false,
"toReturn": [
{
"createTime": "20080806114526000+0800",
"createUser": "张三"
},
{
"createTime": "20080806114526000+0801",
"createUser": "李四"
}
],
"total": 2
}
std::string strTotalNumber = root["total"].asString();
Json::Value subValue = root["toReturn"];
if (subValue.isNull()) return;
size_t count = subValue.size();
for (size_t i = 0; i < count; i++)
{
std::string strCreateTime = subValue[i]["createTime"].asString();
std::string strCreateUser = subValue[i]["createUser"].asString();
}
数据内嵌json文本
一般情况下如下是一个正常的json文本
{
"msg":{
"absTime":1521699455000,
"ext":{"vehicleColor":"A","monitorId":"85ee85b052dd228a08494e11858060f4"}
},
"systemMessage":"1"
}
但是如下,将ext的内容通过双引号包含,变成了一个字符串,解析就需要特别注意
文本内容:
{
"msg":
{
"absTime": 1521699455000,
"ext": "{\"vehicleColor\":\"A\",\"monitorId\":\"85ee85b052dd228a08494e11858060f4\"}"
},
"systemMessage": "1"
}
内存查看
{
"msg":
{
"absTime": 1521699455000,
"ext": "{\"vehicleColor\":\"A\",\"monitorId\":\"85ee85b052dd228a08494e11858060f4\"}"
},
"systemMessage": "1"
}
通过转义来区分双引号,第一步获取到ext对应的字符串,然后重新解析该字符串
Json::Value msg;
Json::Reader readerMsg(Json::Features::strictMode());
if(NULL == readerMsg.parse(strMsg, msg) return;
std::string strExt = msg["ext"].asString();
Json::Value ext;
Json::Reader readerExt(Json::Features::strictMode());
if (readerer.parse(strExt, ext))
{
std::string strColor = ext["vehicleColor"].asString();
std::string strMonitorId = ext["monitorId"].asString();
}
注意:提示字符串中字符无效
jsoncpp 在处理 UTF-8 编码的字符串是没有问题,意思是可以正确的解析出键值对,在VS调试情况下,返回的字符串如果包含中文,会提示:字符串中字符无效,这是因为在VS调试过程中,只支持GBK编码的中文,因此如果需要进行字符串的查看,观察字符串的中文含义,可以先将字符串从UTF-8编码转换为GBK编码,然后就可以进行变量的监视提供UTF-8转GBK
std::string UTF8ToGBK(const char* szUTF8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, szUTF8, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_UTF8, 0, szUTF8, -1, wstr, len);
len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
char* pszStr = new char[len + 1];
memset(pszStr, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wstr, -1, pszStr, len, NULL, NULL);
if (wstr) delete[] wstr;
std::string str = pszStr;
delete[] pszStr;
return str;
}
面对可变键的读取
文本:
{
"errorCode":"0",
"desc":"实时定位数据获取成功",
"data":{
"粤AF01857":{
"vehicleId":"540385513928462336",
"startAddr":""
}
}
}
通过getMemberNames枚举每一个键
Json::Value &dataValue = root["data"];
Json::Value::Members keyMembers = dataValue.getMemberNames();
for (int i=0; i<keyMembers.size(); i++)
{
std::string strKey = keyMembers[i];
Json::Value &valuePlates = dataValue[strKey];
std::string strVehicleId = valuePlates["vehicleId"].asString();
std::cout << strVehicleId << std::endl;
}
toStyledString字符串中文乱码解决方案
场景
在VS下使用JsonCpp把中文赋值给Json::Value后toStyledString()打印,中文字已经变成\u开始的字符,而且还是不准确的unicode码,在Linux环境下没有这个问题。中文乱码显示如下: "VehicleBrand" : "\u5927\u4f17"
解决方案
打开jsoncpp源码jsoncpp.cpp文件,找到valueToQuotedStringN函数修改如下:
static JSONCPP_STRING valueToQuotedStringN(const char* value, unsigned length) {
if (value == NULL)
return "";
if (!isAnyCharRequiredQuoting(value, length))
return JSONCPP_STRING("\"") + value + "\"";
// We have to walk value and escape any special characters.
// Appending to JSONCPP_STRING is not efficient, but this should be rare.
// (Note: forward slashes are *not* rare, but I am not escaping them.)
JSONCPP_STRING::size_type maxsize = length * 2 + 3; // allescaped+quotes+NULL
JSONCPP_STRING result;
result.reserve(maxsize); // to avoid lots of mallocs
result += "\"";
char const* end = value + length;
for (const char* c = value; c != end; ++c) {
switch (*c) {
case '\"':
result += "\\\"";
break;
case '\\':
result += "\\\\";
break;
case '\b':
result += "\\b";
break;
case '\f':
result += "\\f";
break;
case '\n':
result += "\\n";
break;
case '\r':
result += "\\r";
break;
case '\t':
result += "\\t";
break;
// case '/':
// Even though \/ is considered a legal escape in JSON, a bare
// slash is also legal, so I see no reason to escape it.
// (I hope I am not misunderstanding something.)
// blep notes: actually escaping \/ may be useful in javascript to avoid </
// sequence.
// Should add a flag to allow this compatibility mode and prevent this
// sequence from occurring.
default:
{
/*
unsigned int cp = utf8ToCodepoint(c, end);
// don't escape non-control characters
// (short escape sequence are applied above)
if (cp < 0x80 && cp >= 0x20)
result += static_cast<char>(cp);
else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane
result += "\\u";
result += toHex16Bit(cp);
} else { // codepoint is not in Basic Multilingual Plane
// convert to surrogate pair first
cp -= 0x10000;
result += "\\u";
result += toHex16Bit((cp >> 10) + 0xD800);
result += "\\u";
result += toHex16Bit((cp & 0x3FF) + 0xDC00);
}
*/
result += *c;
} break;
}
}
result += "\"";
return result;
}
修改default分支的代码
通过代码可以明白的看到default:里面处理的就是包括中文在内的字符
Json的读取
从文件中读取Json
std::ifstream ifs;
ifs.open("testdata.json", std::ios::binary);
Json::Reader reader(Json::Features::strictMode());
Json::Value root;
if (NULL == reader.parse(ifs, root))
{
ifs.close();
return;
}
ifs.close();
从缓存中读取Json
std::string strText = "{\"total\":1,\"toReturn\":[{\"createTime\":\"20080806114526000+0800\",\"createUser\":\"李四\"}],\"success\":false}";
//std::string strText = "{\r\n\"success\":false,\r\n\"toReturn\":[\r\n{\r\n\"createTime\":\"20080806114526000+0800\",\r\n\"createUser\":\"李四\"\r\n}\r\n],\r\n\"total\":1\r\n}\r\n";
//std::string strText = "{\n \"success\" : false,\n \"toReturn\" : [\n {\n \"createTime\" : \"20080806114526000+0800\",\n \"createUser\" : \"李四\"\n }\n ],\n \"total\" : 1\n}\n";
Json::Reader reader(Json::Features::strictMode());
Json::Value root;
if (NULL == reader.parse(strText, root)) return;
std::string strContext = root.toStyledString();
int nTotal = root["total"].asInt();
第二个添加了回车换行,不影响解析
第三个进行了格式化操作,不影响解析
数字的读取可以当作字符串读取出来
Json::Value valueRoot;
valueRoot["value"] = 45.63455;
std::string strTest = valueRoot.toStyledString();
Json::Reader reader(Json::Features::strictMode());
Json::Value root;
if (reader.parse(strTest, root))
{
std::string strValue;
if (!root["value"].isNull())
{
//本来value的值是一个浮点型,也可以通过转换成字符串读取到变量中
strValue = root["value"].asString();
}
}
读取带BOM的UTF-8编码文本
知识解读
在Windows通过记事本打开的UTF-8编码文件,默认会在保存的时候,往文件开头多添加三个字节EF BB BF,表明文本的编码方式是UTF-8,这种技术就叫做BOM(Byte Order Mark,就是字节序标记)。在Unix或者Linux操作系统中不会出现这种情况。如果该文本是ANSI格式编码的,也没有添加其他的字符。
文本内容读取差异
带有BOM的文本字节流
"锘縖\r\n{\r\n\t\"version\": \"1.0.0\",\r\n\t\"messagetype\": \"alarm\",\r\n\t\"cmdtype\": 10009,\r\n\t\"sn\":\"202039248932482934\"
不带BOM的文本字节流
"[\r\n{\r\n\t\"version\": \"1.0.0\",\r\n\t\"messagetype\": \"alarm\",\r\n\t\"cmdtype\": 10009,\r\n\t\"sn\": \"202039248932482934\"
问题
默认情况下传递带有BOM的文本字节流给JsonCpp解析,肯定是解析不出来的,因为多了EF BB BF三个字节,所以需要将这三个字节从文本中剔除
代码
std::ifstream ifs;
ifs.open(pFileName, std::ifstream::in | std::ifstream::binary);
std::string str((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
std::string strValidJson;
if ((0xef == (unsigned char)str[0]) && (0xbb == (unsigned char)str[1]) && (0xbf == (unsigned char)str[2]))
{
strValidJson = str.substr(3, str.length() - 3);
}
else
{
strValidJson = str;
}
//开始解析Json文本
Json::Reader reader;
Json::Value root;
if (NULL == reader.parse(strJson, root))
{
ifstream.close();
return;
}
构建空对象
构建{}空json字符串
Json::Value valueEmptyObject = Json::objectValue;
std::string strEmptyObject = valueEmptyObject.toStyledString();
结果:{}
构建对象键值空值{}
Json::Value valueEmptyObject;
valueEmptyObject["data"] = Json::objectValue;
std::string strEmptyObject = valueEmptyObject.toStyledString();
结果:{ "data" : {}}
构建空数组对象[]
Json::Value valueEmptyArray;
valueEmptyArray.resize(0);
std::string strEmptyArray = valueEmptyArray.toStyledString();
结果:[]
生成json格式
快速生成json
Json::Value response;
response["code"] = -1;
Json::FastWriter fastWriter;
std::string strResponse = fastWriter.write(response);
格式化生成json
Json::Value response;
response["code"] = -1;
std::string strResponse = response.toStyledString();
1)数据内嵌json
{
"code" : "SheBeiLiXianGaoJingShangChuan",
"params" :
{
"alarm_source" : "192.68.1.0",
"id" : "234",
"remark" : "fire alarm",
"time_alarm" : "2017-2-1 21:34:21"
}
}
Json::Value jsonCode;
jsonCode["code"] ="SheBeiLiXianGaoJingShangChuan";
Json::Value jsonParams;
jsonParams["id"] ="234";
jsonParams["remark"] = "firealarm";
jsonParams["time_alarm"] ="2017-2-1 21:34:21";
jsonParams["alarm_source"] ="192.68.1.0";
jsonCode["params"] = jsonParams;
2)数据内嵌字符串数组
{
"extra_fields":[
"custom_field_1",
"custom_field_2"
],
"surveillance_ids":[
"0"
],
"order":{
"timestamp":-1
},
"hit_condition":{
"hit_similarity":80
},
"start":0,
"limit":100
}
代码
Json::Value root;
Json::Value jsonExtraFields;
jsonExtraFields.append("custom_field_1");
jsonExtraFields.append("custom_field_2");
root["extra_fileds"] = jsonExtraFields;
Json::Value jsonSurveillanceIds;
jsonSurveillanceIds.append("0");
root["surveillance_ids"] = jsonSurveillanceIds;
Json::Value jsonTimestamp;
jsonTimestamp["timestamp"] = -1;
root["order"] = jsonTimestamp;
Json::Value jsonHitSimilarity;
jsonHitSimilarity["hit_similarity"] = 80;
root["hit_condition"] = jsonHitSimilarity;
root["start"] = 0;
root["limit"] = 100;
std::string strResult = root.toStyledString();
3)创建一个标准带有缩进格式的json文件
void WriteContentToJsonFile()
{
Json::Value root;
for (int i = 0; i < 10; i++)
{
Json::Value node;
node["zoom"] = 1;
root["data"].append(node);
}
cout << "直接输出:" << endl;
Json::FastWriter fw;
cout << fw.write(root) << endl ;
//直接输出的没有进行换行,查看困难
cout << "缩进输出:" << endl;
Json::StyledWriter sw;
cout << sw.write(root) << endl ;
ofstream ofstreamHandle;
ofstreamHandle.open("fengyuzaitu51cto.json");
ofstreamHandle << sw.write(root);
ofstreamHandle.close();
}
4)构建一个空数组元素
Json::Value root;
Json::Value nodeRoot;
nodeRoot.resize(0);
root["sessionid"] = "123456";
root["node"] = nodeRoot;
std::string strMsg = root.toStyledString();
内存报文:
{
"node" : [],
"sessionid" : "123456"
}
提醒
Json::Value root;
Json::Value nodeRoot;
Json::Value node;
nodeRoot.append(node);
root["node"] = nodeRoot;
std::string strMsg = root.toStyledString();
生成的报文
{
"node" : [ null ]
}
里面是带有null字段的数组
注意事项
Json::Value valueNodeList;
for(int i=0; i <3; i++)
{
Json::Value nodeChannel;
Json::Value
nodeChannel["id"] = i;
valueNodeList.append(nodeChannel);
}
Json::Value valueRoot;
std::cout<<valueNodeList.size()<<std::endl;\\输出3
valueRoot["channel"] = valueNodeList;
std::cout<<valueNodeList.size()<<std::endl;\\输出0
进行如上的赋值操作之后,不能再调用valueNodeList访问数组,例如调用valueNodeList.size访问到的数据始终返回0,因为数据已经迁移到valueRoot对象中。
代码剖析
valueRoot["channel"] = valueNodeList;这句代码,通过源码剖析
Value& Value::operator=(const Value& other)
{ swap(const_cast<Value&>(other)); return *this; }
void Value::swapPayload(Value& other)
{ ValueType temp = type_; type_ = other.type_; other.type_ = temp; std::swap(value_, other.value_);
int temp2 = allocated_; allocated_ = other.allocated_; other.allocated_ = temp2 & 0x1; } std::swap对值进行了交换,因此再次对valueNodeList的访问,就出现了0的结果
Json::Value引用使用技巧
对Json::Value的等号赋值都会引起原有值的变化
调用std::swap对值进行改变
Value& Value::operator=(const Value& other) {
swap(const_cast<Value&>(other));
return *this;
}
void Value::swapPayload(Value& other) {
ValueType temp = type_;
type_ = other.type_;
other.type_ = temp;
std::swap(value_, other.value_);
int temp2 = allocated_;
allocated_ = other.allocated_;
other.allocated_ = temp2 & 0x1;
}
所以当如下代码执行的时候,将会node将会是空的
Json::Value node;
root["node"] = node;
Json::Value的拷贝性能
Json::Value node = root[i];
这个函数方式将会对数据进行拷贝,影响性能,因此正确的做法是
Json::Value& node = root[i];
注意
1)断言问题
JsonCpp解析非法Json时,会主动容错成字符类型,对字符类型取下标时,会触发assert终止程序。解决的方法:启用严格模式,当解析非法Json时返回false,不再自动容错。创建读取对象的时候,指定特性:Json::Reader reader(Json::Features::strictMode());
不提倡使用 Json::Reader reader;
2)记事本打开问题
Json格式的文本文件用Windows的记事本打开,可能会往文本中添加BOM标识,导致读取失败,建议使用Notepad++进行修改查看
详细解决方案参考:https://blog.51cto.com/fengyuzaitu/2412115 读取带BOM的UTF-8编码文本
3)打开方式
读取文件的时候,需要设置文件的打开方式,std::ios::binary指定了二进制的读取方式,完整的读取所有的数据,这个跟读取视音频文件的方式是一样的
4)Use CharReader and CharReaderBuilder instead
编译出错:error C4996: 'Json::Reader::Reader': Use CharReader and CharReaderBuilder instead : 参见“Json::Reader::Reader”的声明
新版本已经标志Json::Reader::Reader为废弃接口,编译情况下可能会出错提示,根据编译器的不同,而提示不同
解决方案:
打开工程属性,C/C++,常规,SDK检查,选择否
5)可能的内存泄漏使用方式
构建json的时候,出现相同的键例如
json["id"] ="12345";
json["id"] ="123";
导致只有一个id的内存倍释放,这种基本的代码错误需要密切注意