JsonCpp--快速上手使用C++对json文件的读写(Linux)

前言

  • 由于原生态的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); // reader can also read strings
        cout << "Book: " << obj["book"].asString() << endl;
        cout << "Year: " << obj["year"].asUInt() << endl;
        const Json::Value& characters = obj["characters"]; // array of 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)类型
  • 注意事项:
    1. 当你需要从JSON对象中提取原始数据(如字符串)时,应使用.asString(),.asInt()等方法,而不是直接访问成员。
    2. 如果尝试将错误的类型转换为数值类型,如将字符串转换为整数,将会抛出异常。
  • 常用方法:
    • 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对象,初始化为null
        Json::Value nullValue;
        std::cout << nullValue.toStyledString() << std::endl; // 输出: null
    
        // 创建一个Json::Value对象并设置为对象类型
        Json::Value rootValue;
        rootValue["name"] = "John Doe";
        rootValue["age"] = 30;
        rootValue["isStudent"] = false;
    
        // 输出整个对象
        std::cout << rootValue.toStyledString() << std::endl;
    
        // 提取对象中的值----注意事项1
        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"); // 移除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对象,用于存储解析后的数据
        Json::Value rootValue;
        // 创建一个JSON字符串
        std::string jsonString = "{\"name\":\"John Doe\", \"age\":30, \"isStudent\":false}";
        // 创建一个Json::Reader对象
        Json::Reader reader;
        // 尝试解析JSON字符串
        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对象,用于存储解析后的数据
        Json::Value rootValue;
        // 打开JSON文件
        std::ifstream file("example.json");
        if (!file.is_open()) {
            std::cerr << "Error opening file" << std::endl;
            return 1;
        }
        // 创建一个Json::Reader对象
        Json::Reader reader;
        // 尝试解析JSON文件
        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常用的方法:
    // omit the word "null" when printing null values
    // this contradicts the JSON standard, but accepted by JavaScript
    // this function is not available in old versions
    void dropNullPlaceholders();
    
    // don't add newline as last character
    // this function is not available in old versions
    void omitEndingLineFeed();
    
    // print space after ":" in objects
    
    void enableYAMLCompatibility();
    
    // write JSON object to a string
    virtual std::string write(const Value &root);
    
    
  • 使用例子
    #include <jsoncpp/json/json.h> 
    #include <iostream>
    int main() {
        // 创建一个Json::Value对象,用于存储要写入的数据
        Json::Value rootValue;
        rootValue["name"] = "John Doe";
        rootValue["age"] = 30;
        rootValue["isStudent"] = false;
        // 创建一个Json::FastWriter对象
        Json::FastWriter writer;
        // 写入Json::Value对象到字符串
        std::string jsonString = writer.write(rootValue);
        // 输出JSON字符串
        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)
    //使用Json::FastWriter写入文件
    {
        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)
    //使用Json::Reader读取文件
    {
        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)) 
        {
            // 确保JSON结构中存在"coordinates"数组
            if (value.isArray() && value.size() > 0) 
            {
                for (const Json::Value& coordinate : value) 
                {
                    // 提取x和y值
                    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
    {
        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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值