前言
由于原生态的C++库并不支持JSON文件的直接读写,故本文引入JsonCpp库来辅助我们对JSON文件进行读写
JSON简介
JSON (JavaScript Object Notation),是一种轻量级数据交换格式。 JSON基于两种结构
键值对(A collection of name/value pairs) 值有序链表(An ordered list of values) 在JSON中,通常有一下几种形式:
object:一种无序的键-值(key:value)配对。键都代表着一个JSON的字符串,而值可以是任意类型{
"student" : {
"name" : "Lihua" ,
"age" : 20 ,
"married" : false
}
}
array:是一个值的有序链表,可以存储多种类型。[ "Sunday" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" ]
JsonCpp简介
JsonCpp是一个C++库,允许操作JSON值,包括对字符串的序列化和反序列化。它还可以在非序列化/序列化步骤中保留现有注释,使其成为存储用户输入文件的方便格式。
通过apt一键安装JsonCpp
JsonCpp有多种下载方法,这里我们使用较为简单的软件包安装(ubuntu及其其他Debian Linux版本)sudo apt-get install libjsoncpp-dev
下载后使用JsonCpp只需包含其头文件即可#include <jsoncpp/json/json.h>
编译只需要添加标志位-ljsoncpp g++ -o excutable_name source.cpp -ljsoncpp
在CMakeLists.txt中配置:find_package ( PkgConfig REQUIRED)
pkg_check_modules ( JSONCPP jsoncpp)
include_directories ( ${ JSONCPP_LIBRARIES} )
target_link_libraries ( ${ PROJECT_NAME} ${ JSONCPP_LIBRARIES} )
前置知识
Stream Classes in C++
概念:Stream(流)是一种电脑对输入/输出处理的代表方式。 而fstream提供了一系列库和方法来处理文件(file)
ofstream:输出流(output stream),创建文件和向文件写入数据 ifstream:输入流(input stream),从文件读取数据并且展示他们 fstream:文件流(file stream),定义了一些文件操作类常用的流,它有上述两种流的能力
一个例子快速入门
创建一个alice.json文件{
"book" : "Alice in Wonderland" ,
"year" : 1865 ,
"characters" :
[
{ "name" : "Jabberwock" , "chapter" : 1 } ,
{ "name" : "Cheshire Cat" , "chapter" : 6 } ,
{ "name" : "Mad Hatter" , "chapter" : 7 }
]
}
读取json文件# include <iostream>
# include <fstream>
# include <jsoncpp/json/json.h>
using namespace std;
int main ( ) {
ifstream ifs ( "alice.json" ) ;
Json:: Reader reader;
Json:: Value obj;
reader. parse ( ifs, obj) ;
cout << "Book: " << obj[ "book" ] . asString ( ) << endl;
cout << "Year: " << obj[ "year" ] . asUInt ( ) << endl;
const Json:: Value& characters = obj[ "characters" ] ;
for ( int i = 0 ; i < characters. size ( ) ; i++ ) {
cout << " name: " << characters[ i] [ "name" ] . asString ( ) ;
cout << " chapter: " << characters[ i] [ "chapter" ] . asUInt ( ) ;
cout << endl;
}
}
输出:Book : Alice in Wonderland
Year : 1865
name : Jabberwock chapter: 1
name : Cheshire Cat chapter: 6
name : Mad Hatter chapter: 7
JsonCpp的三大主要类别
1.Json::Value
说明:代表一个JSON值(value)类型 注意事项:
当你需要从JSON对象中提取原始数据(如字符串)时,应使用.asString(),.asInt()等方法,而不是直接访问成员。 如果尝试将错误的类型转换为数值类型,如将字符串转换为整数,将会抛出异常。 常用方法:
isObject(), isArray(), isString(), isBool(), isNumber(): 这些方法用于检查Json::Value的类型。 getObject(), getArray(), getString(), getBool(), getUInt(): 这些方法用于获取相应的类型数据,如果类型不匹配,这些方法会抛出异常。 toStyledString(): 将JSON对象转换为格式化的字符串。 append: 向数组类型的JSON值添加一个元素。 put: 向对象类型的JSON值添加或修改一个成员。 remove: 从对象中移除一个成员。 例子# include <jsoncpp/json/json.h>
# include <iostream>
int main ( ) {
Json:: Value nullValue;
std:: cout << nullValue. toStyledString ( ) << std:: endl;
Json:: Value rootValue;
rootValue[ "name" ] = "John Doe" ;
rootValue[ "age" ] = 30 ;
rootValue[ "isStudent" ] = false ;
std:: cout << rootValue. toStyledString ( ) << std:: endl;
std:: string name = rootValue[ "name" ] . asString ( ) ;
int age = rootValue[ "age" ] . asInt ( ) ;
bool isStudent = rootValue[ "isStudent" ] . asBool ( ) ;
std:: cout << "Name: " << name << std:: endl;
std:: cout << "Age: " << age << std:: endl;
std:: cout << "Is Student: " << std:: boolalpha << isStudent << std:: endl;
rootValue[ "age" ] = 31 ;
rootValue. remove ( "isStudent" ) ;
Json:: ValueArray hobbies;
hobbies. append ( "Reading" ) ;
hobbies. append ( "Swimming" ) ;
rootValue[ "hobbies" ] = hobbies;
std:: cout << rootValue. toStyledString ( ) << std:: endl;
return 0 ;
}
一个特殊例子----当我们有一个嵌套例子时,我们要通过value[arraykey][index][subkey]来访问其内容 {
"media" : [
{
"book"
} ,
{
"title" : "The Great Gatsby" ,
"author" : "F. Scott Fitzgerald"
} ,
{
"movie"
} ,
{
"music"
}
]
}
// 例如,我们要访问media数组中的第二个元素(索引为1),其title键的值
std::string title = arrayOfObjects[1]["title"].asString();
2.Json::Reader
说明:提供了一种强力的读取JSON文件的手段 注意事项
确保JSON字符串或文件内容是有效的,否则Json::Reader在解析时会抛出异常。 使用Json::Reader时,字符串字面量中的特殊字符(如引号和反斜杠)需要正确转义。例如,如果JSON中的字符串包含引号,你应该使用转义引号"。 常用方法:
parse: 这是主要的解析方法,接受一个std::string或std::istream作为输入,并返回一个Json::Value对象。 parseFromFile: 解析一个JSON文件,返回一个Json::Value对象。 isValid: 检查JSON字符串是否有效。 getError: 获取解析过程中的错误信息。 下面是一个使用Json::Reader解析JSON字符串的例子:
# include <jsoncpp/json/json.h>
# include <iostream>
# include <fstream>
int main ( ) {
Json:: Value rootValue;
std:: string jsonString = "{\"name\":\"John Doe\", \"age\":30, \"isStudent\":false}" ;
Json:: Reader reader;
bool parsingSuccessful = reader. parse ( jsonString, rootValue) ;
if ( parsingSuccessful) {
std:: cout << "Name: " << rootValue[ "name" ] . asString ( ) << std:: endl;
std:: cout << "Age: " << rootValue[ "age" ] . asInt ( ) << std:: endl;
std:: cout << "Is Student: " << rootValue[ "isStudent" ] . asBool ( ) << std:: endl;
} else {
std:: cout << "Failed to parse JSON: " << reader. getError ( ) << std:: endl;
}
return 0 ;
} ```
当然我们也可以使用ifstream配合使用Json::Reader# include <jsoncpp/json/json.h>
# include <iostream>
# include <fstream>
int main ( ) {
Json:: Value rootValue;
std:: ifstream file ( "example.json" ) ;
if ( ! file. is_open ( ) ) {
std:: cerr << "Error opening file" << std:: endl;
return 1 ;
}
Json:: Reader reader;
bool parsingSuccessful = reader. parse ( file, rootValue, true ) ;
file. close ( ) ;
if ( parsingSuccessful) {
std:: cout << "Name: " << rootValue[ "name" ] . asString ( ) << std:: endl;
std:: cout << "Age: " << rootValue[ "age" ] . asInt ( ) << std:: endl;
} else {
std:: cout << "Failed to parse JSON: " << reader. getFormattedErrorMessages ( ) << std:: endl;
}
return 0 ;
}
3.Json::Writer
注意Json::Writer是一个抽象类,其中包含了下面两个类
FastWriter:生成未格式化的、非人类可读的文档。所有内容都将写在一行中。 StyledWriter:生成格式化的可读文档,类似于运算符<<,但缩进较少,没有空行。 这里介绍FastWriter,以下是FastWriter常用的方法:
void dropNullPlaceholders ( ) ;
void omitEndingLineFeed ( ) ;
void enableYAMLCompatibility ( ) ;
virtual std:: string write ( const Value & root) ;
使用例子# include <jsoncpp/json/json.h>
# include <iostream>
int main ( ) {
Json:: Value rootValue;
rootValue[ "name" ] = "John Doe" ;
rootValue[ "age" ] = 30 ;
rootValue[ "isStudent" ] = false ;
Json:: FastWriter writer;
std:: string jsonString = writer. write ( rootValue) ;
std:: cout << jsonString << std:: endl;
return 0 ;
}
综合使用
假设我们有一个巨大的数据需要通过JSON文件存储,并在另一个节点读取它
# include <iostream>
# include <sstream>
# include <fstream>
# include <jsoncpp/json/json.h>
class JSON
{
public :
void json_writer ( Json:: Value context, const std:: string& open_file_path) ;
void json_reader ( const std:: string& open_file_path) ;
private :
bool is_file_empty ( std:: ifstream& pFile) ;
std:: string file_name= "example.json" ;
} ;
bool JSON :: is_file_empty ( std:: ifstream& pFile)
{
return pFile. peek ( ) == std:: ifstream:: traits_type:: eof ( ) ;
}
void JSON :: json_writer ( Json:: Value context, const std:: string& open_file_path)
{
if ( context. isNull ( ) )
{
std:: cout << "写入的内容是空的" << std:: endl;
return ;
}
std:: ifstream file_ ( open_file_path) ;
if ( ! is_file_empty ( file_) )
{
std:: cout << "文件不为空,是否选择覆盖?(y/n)" << std:: endl;
char userInput;
std:: cin >> userInput;
if ( userInput == 'y' || userInput == 'Y' )
{
std:: cout << "用户选择覆盖。" << std:: endl;
} else if ( userInput == 'n' || userInput == 'N' )
{
std:: cout << "用户选择不覆盖。" << std:: endl;
return ;
} else
{
std:: cout << "无效的输入。请输入 'y' 或 'n'。" << std:: endl;
return ;
}
}
Json:: FastWriter write;
auto str = context. toStyledString ( ) ;
std:: ofstream ofss;
ofss. open ( open_file_path) ;
ofss << str;
ofss. close ( ) ;
std:: cout << "写入成功!" << std:: endl;
}
void JSON :: json_reader ( const std:: string& open_file_path)
{
std:: ifstream ifs;
ifs. open ( open_file_path) ;
std:: stringstream buffer;
buffer << ifs. rdbuf ( ) ;
ifs. close ( ) ;
auto str = buffer. str ( ) ;
Json:: Reader reader;
Json:: Value value;
if ( reader. parse ( str, value) )
{
if ( value. isArray ( ) && value. size ( ) > 0 )
{
for ( const Json:: Value& coordinate : value)
{
int x = coordinate[ "x" ] . asInt ( ) ;
int y = coordinate[ "y" ] . asInt ( ) ;
std:: cout << "Coordinate x: " << x << ", y: " << y << std:: endl;
}
} else
{
std:: cerr << "JSON does not contain a 'coordinates' array." << std:: endl;
}
} else
{
std:: cerr << "Failed to parse JSON." << std:: endl;
}
}
Json:: Value createLargeCoordinateArray ( )
{
Json:: Value coordinates;
for ( int i = 0 ; i < 100 ; ++ i)
{
Json:: Value coordinate;
coordinate[ "x" ] = i;
coordinate[ "y" ] = i * 2 ;
coordinate[ "linear_X" ] = static_cast < int > ( i * 0.1 ) ;
coordinate[ "angular_Z" ] = static_cast < int > ( i * 0.2 ) ;
coordinates. append ( coordinate) ;
}
return coordinates;
}
int main ( )
{
JSON json= JSON ( ) ;
Json:: Value context= createLargeCoordinateArray ( ) ;
std:: string file_path= "example3.json" ;
json. json_writer ( context, file_path) ;
json. json_reader ( file_path) ;
return 0 ;
}
参考文章
https://www.json.org/json-en.html https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp/+/c21b4bbfdb17713af9bcd194106f69aaa289e72b/README.md https://www.cnblogs.com/DswCnblog/p/6678708.html https://en.wikibooks.org/wiki/JsonCpp#With_apt_or_apt-get https://www.simplilearn.com/tutorials/cpp-tutorial/ifstream-in-cpp https://www.daniweb.com/programming/software-development/threads/250669/check-if-file-is-empty https://stackoverflow.com/questions/2390912/checking-for-an-empty-file-in-c https://www.cnblogs.com/zipxzf/articles/14909393.html