c++中json文件的使用

一.json.hpp库下载及安装

1.1 开源地址及引入方法

nlohmann json的开源项目地址,其中有对json使用方法的详细说明:

https://github.com/nlohmann/json#serialization–deserialization

​ 对于我们项目中要使用nlohmann json工具,只需要引入json.hpp这一个文件,其中包含所有接口函数,正如其文档中所述json.hpp文件在single_include/nlohmann目录下,我们只需要下载该文件即可:

git clone https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp

在这里插入图片描述

上图片所示,使用json.hpp文件需要关注两点:
 一是:#include <nlohmann/json.hpp>头文件路径的引入,这里将json.hpp文件放到linux系统中的/usr/local/include路径下,这是系统默认头文件路径,在编译时系统会自动查找该路径。我们在/usr/local/include路径下创建/nlohmann/json.hpp,如下图所示:
 二是:在编译时需要指定c++11标准,-std=c++11。

1.2 demo程序测试

jsontest.cpp:

#include <iostream>
#include <nlohmann/json.hpp>   //引入json.hpp,该文件已经放在系统默认路径:/usr/local/include/nlohmann/json.hpp
using namespace std;
// for convenience
using json = nlohmann::json;

int main() 
{
  auto config_json = json::parse(R"({"happy": true, "pi": 3.141})");  //构建json对象
  cout << config_json << endl;   //输出json对象值
  return 0;
}

编译:

g++ jsontest.cpp -std=c++11

输出结果:

二.nlohmann json基本操作

2.1 由basic value创建json

两种方式创建json对象:赋值构造+直接构造

jsontest.cpp:

#include <iostream>
#include <nlohmann/json.hpp>   //引入json.hpp,该文件已经放在系统默认路径:/usr/local/include/nlohmann/json.hpp

using namespace std;
using json = nlohmann::json;    // for convenience 

int main() 
{
  //方式一:赋值构造
  json j1;
  j1["name"]="LeBorn Jame";//字符串  
  j1["number"]=23;         //整数
  j1["man"]=true;          //布尔值
  j1["children"]={"LeBorn Jr","Bryce Maximus","Zhuri"};//数组
  j1["behavior"]["funny"]="gigigigigigi";              //对象中元素值
  j1["wife"]={{"name","Savannah Brinson"},{"man",false}};//对象
  
  //方式二:直接构造
  json j2={
    {"name","LeBorn Jame"},
    {"number",23},
    {"man",true},
    {"children",{"LeBorn Jr","Bryce Maximus","Zhuri"}},
    {"behavior",{{"funny","gigigigigigi"}}},
    {"wife",{{"name","Savannah Brinson"},{"man",false}}}
  };

  cout << "j1: "<<j1 << endl;   //输出json对象值
  cout <<  "j2: "<<j2 << endl;   //输出json对象值
  return 0;
}

编译:

g++ jsontest.cpp -std=c++11

输出结果:

j1: {“behavior”:{“funny”:“gigigigigigi”},“children”:[“LeBorn Jr”,“Bryce Maximus”,“Zhuri”],“man”:true,“name”:“LeBorn Jame”,“number”:23,“wife”:{“man”:false,“name”:“Savannah Brinson”}}
j2: {“behavior”:{“funny”:“gigigigigigi”},“children”:[“LeBorn Jr”,“Bryce Maximus”,“Zhuri”],“man”:true,“name”:“LeBorn Jame”,“number”:23,“wife”:{“man”:false,“name”:“Savannah Brinson”}}

2.2 由json对象得到basic value

#include <iostream>
#include <string>

#include <nlohmann/json.hpp>   //引入json.hpp,该文件已经放在系统默认路径:/usr/local/include/nlohmann/json.hpp

using namespace std;

using json = nlohmann::json;    // for convenience 

int main() 
{
  //构建一个json对象hututu
  json hututu = {
      {"name","hututu"},
      {"age",18},
      {"gender",'m'},
      {"score",88.99},
      {"location",{"aaa","bbb","ccc"}},
  };
  //方式一
  auto name = hututu["name"].get<std::string>();  //获取“name”对应的value值,并转为string类型
  cout<<"name = "<<name<<endl;
  cout<<"type name = "<<typeid(name).name()<<endl;
  cout<<"----------------------"<<endl;
  //方式二
  auto location0 = hututu["location"][0].get<std::string>();
  auto location1 = hututu["location"][1].get<std::string>();
  auto location2 = hututu["location"].at(2).get<std::string>();
  cout<<"location0 = "<<location0<<endl;
  cout<<"location1 = "<<location1<<endl;
  cout<<"location2 = "<<location2<<endl;
  return 0;
}

输出结果:

name = hututu
type name = Ss
location0 = aaa
location1 = bbb
location2 = ccc

2.3 像操作stl container一样操作json value

#include <iostream>
#include <string>

#include <nlohmann/json.hpp>   //引入json.hpp,该文件已经放在系统默认路径:/usr/local/include/nlohmann/json.hpp

using namespace std;

using json = nlohmann::json;    // for convenience 

int main() 
{
  //构建一个json对象animalArray
  json animalArray={"cat","dog"};//定义一个数组类型的json对象
  animalArray.push_back("pig");//添加元素
  animalArray.emplace_back("duck");//C++11新方式添加元素,减少申请内存
  cout<<"animalArray: "<<animalArray<<endl;
  //使用is_array()函数判断对象类型,使用empty函数判断数量是否为空
  if(animalArray.is_array() && !animalArray.empty())
  {
      auto size=animalArray.size();          //使用size函数获取元素数量
      cout<<"animalArray size: "<<size<<endl;
      auto animalLast=animalArray.at(size-1).get<std::string>();
      cout<<"animalArray[size-1]: "<<animalLast<<endl;
      cout<<"/--------------------/"<<endl;
  }

  json animalObject={{"kind","dog"},{"height",50}};//定义一个对象类型的json对象
  animalObject.push_back({"color","red"});//插入元素
  animalObject.erase("kind");//删除键值
  cout<<"animalObject: "<<animalObject<<endl;
  animalObject["height"] = 99;   //通过key修改value值
  //判断是否含有某个键值方式一
  if(animalObject.contains("height"))//通过contains函数判断是否包含某个key
  {
      auto height=animalObject["height"].get<double>();
      cout<<"方式一:height: "<<height<<endl;
  }
  //判断是否含有某个键值方式二
  auto size=animalObject.count("height");//通过count函数计算某一个键的数量
  if(size>0)
  {
      cout<<"方式二:存在height键值"<<endl;
  }
  //判断是否含有某个键值方式三
  auto iter=animalObject.find("height");//通过find函数查找某个键的迭代器
  if(iter!=animalObject.end())
  {
      cout<<"方式三:存在height键值"<<endl;
  }
  //遍历输出键值方式1
  cout<<"遍历输出键值方式1:"<<endl;
  for(auto item:animalObject.items())
  {
      std::cout<<item.key()<<" "<<item.value()<<std::endl;
  }
  //遍历输出键值方式2
  cout<<"遍历输出键值方式2:"<<endl;
  for(auto iter=animalObject.begin();iter!=animalObject.end();++iter)
  {
      cout<<iter.key()<<" "<<iter.value()<<std::endl;
  }
  return 0;
}

输出结果:

animalArray: [“cat”,“dog”,“pig”,“duck”]
animalArray size: 4
animalArray[size-1]: duck
/--------------------/
animalObject: {“color”:“red”,“height”:50}
方式一:height: 99
方式二:存在height键值
方式三:存在height键值
遍历输出键值方式1:
color “red”
height 99
遍历输出键值方式2:
color “red”
height 99

三.json序列化与反序列化(文件操作)

3.1 json对象和文件输入输出转换

#include <iostream>        //文件操作头文件
#include <string>
#include <fstream>
#include <nlohmann/json.hpp>   //引入json.hpp,该文件已经放在系统默认路径:/usr/local/include/nlohmann/json.hpp

using namespace std;

using json = nlohmann::json;    // for convenience 

int main() 
{
  //上述操作适用于istream和ostream的子类,比如我们经常会用到的ifstream和ofstream
  //从.json文件中读取内容到json对象中
  std::ifstream in("./person.json");//打开文件,关联到流in
  json hututu={"111","222"};      //定义一个json对象为hututu,有初始内容,但是会被覆盖
  in>>hututu;                     //从流in中(也就是./person.json文件)读取内容到json对象中,会覆盖之前内容
  in.close();                     //关闭文件流in
  hututu["aaa"]="bbb";             //添加json对象内容
  cout << hututu << endl;   //输出json对象值

  //输出json对象内容到文件中,并生成新的文件
  std::ofstream out("./new.json");    //创建文件./new.json,并关联到流out
  hututu["name"]="new name";          //更改hututu对象的内容
  out<<std::setw(4)<<hututu;          //输出json对象hututu信息到文件./new.json中,std::setw(4)用于设置增加打印空格
  out.close();         //关闭文件流out

  return 0;
}

./person.json文件内容

{
“name”:“hututu”,
“age”:18,
“gender”:“m”,
“score”:88.99
}

执行程序后,输出的json对象内容如下,也就是从./person.json文件中读取的信息:

同时在当前目录下生成新的文件./new.json,内容如下所示:

{
“aaa”: “bbb”,
“age”: 18,
“gender”: “m”,
“name”: “new name”,
“score”: 88.99
}

3.2 json value和自定义类直接赋值

在自定义类命名空间中定义两个函数即可像basic value一样进行反序列化和序列化:from_json(const json& j,T& value)、to_json(json& j,const T& value)

需要将 to_json 和 from_json 以及要进行转换的结构体,写到同一命名空间下;

#include <iostream>        //文件操作头文件
#include <string>
#include <nlohmann/json.hpp>   //引入json.hpp,该文件已经放在系统默认路径:/usr/local/include/nlohmann/json.hpp

using namespace std;

using json = nlohmann::json;

class person
{
    public:
        person(){}       //默认构造函数
        person(string m_name,int m_age,double m_score):name(m_name),age(m_age),score(m_score){};
    public:
      string name;
      int age;
      double score;
    void display()
    {
      cout<<"person name = "<<this->name<<endl;
      cout<<"person age = "<<this->age<<endl;
      cout<<"person score = "<<this->score<<endl;
    }
};
//定义from_json(const json& j,T& value)函数,用于序列化
//json对象----->class对象
void from_json(const json& j,person& hututu)
{
    hututu.name=j["name"].get<std::string>();
    hututu.age=j["age"].get<int>();
    hututu.score=j["score"].get<double>();
}

//定义to_json(json& j,const T& value)函数,用于反序列化
//class对象----->json对象
void to_json(json& j,const person& hututu)
{
    j["name"]=hututu.name;
    j["age"]=hututu.age;
    j["score"]=hututu.score;
}
// void to_json(json& j, const person& p) 
// {
// 	j = json{ {"name", p.name}, {"address", p.address}, {"age", p.age} };
// }
 
// void from_json(const json& j, person& p) {
// 	j.at("name").get_to(p.name);
// 	j.at("address").get_to(p.address);
// 	j.at("age").get_to(p.age);
// }

//main.cpp文件
int main()
{
    person hututu{"hututu",18,88.99};//定义一个person对象为hututu
    cout<<"/----------to json,方式1:json=class隐式转换-----------/"<<endl;
    json j1=hututu;                //class to json,隐式调用to_json函数 
    cout<<"j1 = "<<j1<<endl;       //输出json对象值
    cout<<"/----------to json,方式2:调用to_json函数-----------/"<<endl;
    json j2;
    to_json(j2,hututu);          //to json,调用to_json函数
    cout<<"j2 = "<<j2<<endl;     //输出json对象值
    
    cout<<"/----------from json,方式1:调用from_json函数-----------/"<<endl; 
    j1["name"]="new name";        //修改json对象数据
    cout<<"new j1 = "<<j1<<endl;        //输出json对象值
    person hututu_new;
    from_json(j1,hututu_new);   //json---->class
    hututu_new.display();     //输出person对象内容
    cout<<"/----------from json,方式2:调用.get函数-----------/"<<endl; 
    person hututuNew = j2.get<person>();//像basic value一样通过get函数获取值,将其值直接赋值给自定义对象
    hututuNew.display();
    return 0;
}

3.3 较复杂的嵌套json字符串数据对象建模

现有json字符串格式如下:

{
	"teacher_name": "wangwu",
	"student_list": 
	[
		{
		"name": "zhangsan",
		"age": 16,
		"phone": "12345678"
		},
		{
		"name": "lisi",
		"age": 17,
		"phone": "123456"
		}
	]
}

对于较为复杂的嵌套json字符串,通用的做法是对其进行建模,对应到C++则是结构体/类

  1. json字符串分析

直观的看的话, 此json字符串包含两种两大部分:teacher_name和student_list, 其中student_list是一个数组。student_list嵌套的内容包含name、age和phone3个字段

​ 2.建模

​ 2.1 最里层的表示学生对象的json字符串可转成对应的 student 类/结构体

class student
{
    public:	
        string name;
        int age;
        string phone;
};

​ 2.2 外层的表示班主任及学生列表的对象可转换成对应的class_room类/结构体, 其中学生列表可以用vector容器表示

class class_room 
{
    public:
        string teacher_name;
        std::vector<student> student_list;
};

​ 2.3 为了能够对这两个模型进行自由转换,需要对每个类/结构体分别提供两个函数

// 对于student对象
void from_json(const json& j, student& p)
{
    j.at("name").get_to(p.name);
    j.at("age").get_to(p.age);
    j.at("phone").get_to(p.phone);
}

void to_json(json& j, const student& s)
{
	j = json{ { "name", s.name },{ "age",s.age },{ "phone", s.phone } };
}


//对于class_room对象
void from_json(const json& j, class_room& p)
{
    j.at("teacher_name").get_to(p.teacher_name);

    for (auto &student_t : j["student_list"])
    {
        student s;
        from_json(student_t, s);
        p.student_list.push_back(s);
    }
}

void to_json(json& j, const class_room& s)
{
    j = json{ {"teacher_name", s.teacher_name } };
    for (auto & student_t : s.student_list)
    {
        json j_student;
        to_json(j_student, student_t);
        j["student_list"].push_back(j_student);
    }
}

当使用class_room字符串调用json的构造函数时,相应的to_json函数就会被调用,调用完成后对应的json对象就解析完成。反过来,如果调用get或者get_to时,from_json方法将会被调用

​ 2.4注意事项:

  1. 上面3步的都应包含在同一个namespace中,否则nlohmann库将不能正确的进行转换。代码结构示例如下
namespace ns
{
    class A
    {
    	// 一些结构体变量
    }
    void from_json(const json& j, A& a)
    {
    	// 转换的代码
    }
    void to_json(json& j, const A& a)
    {
    	// 转换的代码
    }
}

​ 2. 需要用到这些转换的功能时,相应的头文件必须被包含,否则也不能转换成功

3. 访问json的元素时,尽量使用 ***at()*** 方法而不要使用 ***operator[]***, 因为如果当有key不存在时,***at()*** 方法会抛出异常而 ***operator[]*** 不会

例子:

{
    "output": {
        "width": 720,
        "height": 1080,
        "frameRate": 20,
        "crf": 31
    },
    "tracks": [
        {
            "name": "t1",
            "pieces": [
                {
                    "file": "x.mp4",
                    "startTime": 2,
                    "endTime": 6
                },
                {
                    "file": "y.mp4",
                    "startTime": 9,
                    "endTime": 13
                }
            ]
        },
        {
            "name": "t2",
            "pieces": [
                {
                    "file": "z.mp4",
                    "startTime": 0,
                    "endTime": 10
                }
            ]
        }
    ]
}

从这个 json 文件,人脑解析的话,我们可以看到,它包括两大部分 "output" 和 "tracks"。其中,output 可以用结构体来表示,其代码如下:

struct video_info {
   int width;
   int height;
   int frame_rate;
   int crf;
}

而另一部分可看作是有两个元素的结构体 "tracks" 的数组,其结构体包括 string 的 name,和另一个结构体 "pieces" 的数组。用 C++ 代码可表示为如下:

 struct pieceinfo {
        string  pathname;
        int     startTime;
        int     endTime;
    };
    struct trackinfo {
        string      name;
        pieceinfo   pieces[10];
        int         size;   // piceces 大小
    };

为了解析结构体类内容,需要定义对结构体的解析方法。因此,整个解析过程如下

#include "json.hpp"
#include <iostream>
#include <fstream>

using namespace std;
using json = nlohmann::json;

namespace jsonns {
    struct videoinfo {
        int width;
        int height;
        int frameRate;
        int crf;
    };

    void from_json(const json& j, videoinfo& v) {
        j.at("width").get_to(v.width);
        j.at("height").get_to(v.height);
        j.at("frameRate").get_to(v.frameRate);
        j.at("crf").get_to(v.crf);
    }

    struct pieceinfo {
        string  pathname;
        int     startTime;
        int     endTime;
    };
    
    void from_json(const json&j, pieceinfo &p) {
        j.at("file").get_to(p.pathname);
        j.at("startTime").get_to(p.startTime);
        j.at("endTime").get_to(p.endTime);
    }

    struct trackinfo {
        string      name;
        pieceinfo   pieces[10];
        int         size;
    };

    void from_json(const json&j, trackinfo &t) {
        j.at("name").get_to(t.name);
        for(int i = 0; i < j["pieces"].size(); i++) {
            t.pieces[i] = j["pieces"][i];
        }
        t.size = j["pieces"].size();
    }
}

int main()
{
    json j;
    ifstream jfile("test.json");
    jfile >> j;
    jsonns::videoinfo vi = j.at("output");
    int tilength = j["tracks"].size();
    jsonns::trackinfo ti[tilength];
    for (int i = 0; i < tilength; i++) {
        ti[i] = j["tracks"][i];
    }
    return 0;
}

参考文章

c++中nlohmann json的使用

【C++ JSON 开源库】nlohmann入门使用总结

[C++]-JSON库nlohmann简介

C++使用nlohmann/json 转 C++ 结构体

posted @ 2022-10-10 21:43  北极星!  阅读(1034)  评论(0编辑  收藏  举报