什么是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的内存倍释放,这种基本的代码错误需要密切注意