Bootstrap

使用c++版本nlohmann::json库快速写入、读取json文件

使用c++版本nlohmann::json库快速写入、读取json文件

一、前言

nlohmann::json库本身提供了一些宏,能够实现快速实现序列化、反序列化的json目的。比如:

#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)              // (1)
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...) // (2)

查看头文件可知(1)这个宏添加了两个友元函数负责实现序列化与反序列化。

#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...)  \
    friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \
    friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }

使用此宏需满足两个条件:

1)Type必须有默认的构造函数,
2)宏必须要在类中(class/struct)使用。

并且需要注意的是:

1) 当前的实现被限制为最多64个成员变量。如果要序列化/反序列化具有超过64个成员变量的类型,则需要手动定义to_json/from_json函数。
2) 宏只适用于nlohmann::json类型;目前不支持nlohmann::ordered_json等其他类型。

二、代码实例

以下代码使用NLOHMANN_DEFINE_TYPE_INTRUSIVE宏实现读写配置文件output.json。初次使用请先下载nlohmann::json库,将json.hpp放置在
/usr/local/include路径或者其它路径下,并且需要安装好boost库。

1.main.cpp
#include <iostream>
#include <boost/filesystem.hpp>
#include <nlohmann/json.hpp>

#define debug_info(x) std::cout << (x) << std::endl
#define debug_var(x) std::cout << #x << "=" << (x) << std::endl

using json = nlohmann::json;

class QrDataSt {
public:
    QrDataSt () = default;

    QrDataSt(int pose_id, int qr_id, double qr_x, double qr_y, double qr_deg) :
            poseIndex(pose_id), qrIndex(qr_id), qrX(qr_x), qrY(qr_y), qrDeg(qr_deg) {
    }

    void print() {
        debug_var(poseIndex);
        debug_var(qrIndex);
        debug_var(qrX);
        debug_var(qrY);
        debug_var(qrDeg);
    }

    NLOHMANN_DEFINE_TYPE_INTRUSIVE(QrDataSt, poseIndex, qrIndex, qrX, qrY, qrDeg)

private:
    int poseIndex{};
    int qrIndex{};
    double qrX{};
    double qrY{};
    double qrDeg{};
};

class RobotDataSt {
public:
    RobotDataSt() = default;

    explicit RobotDataSt(std::vector<QrDataSt> &data) : qrDataSt(data) {
    }

    void print() {
        for (auto item : qrDataSt) {
            item.print();
        }
    }

    NLOHMANN_DEFINE_TYPE_INTRUSIVE(RobotDataSt, qrDataSt);

private:
    std::vector<QrDataSt> qrDataSt;
};

bool writeJson(const std::string &filename, RobotDataSt &data) {
    json j = data;
    std::ofstream out(filename);
    if (out.is_open()) {
        if (out.good()) {
            out << j.dump(4) << std::endl; //格式化输出
        } else {
            debug_info("流错误!");
        }
        out.flush();
        out.close();
    } else {
        debug_info("打开文件失败:" + filename);
        return false;
    }
    return true;
}

bool readJson(const std::string &filename, RobotDataSt &data)
{
    json j;
    std::ifstream in(filename);
    if (!in.is_open()) {
        debug_info("打开文件失败:" + filename);
        return false;
    }
    try {
        in >> j;
    } catch (json::exception &e) {
        debug_var(e.what());
        in.close();
        return false;
    }
    data = j.get<RobotDataSt>();
    return true;
}


int main() {

    const std::string outFilename("./output.json");
    QrDataSt mQrDataSt1(1, 110, 955, -939.167, 0);
    QrDataSt mQrDataSt2(2, 120, 2149.88, -344.329, 0);
    std::vector<QrDataSt> mQrDataBuffer;
    mQrDataBuffer.reserve(2);
    mQrDataBuffer.emplace_back(mQrDataSt1);
    mQrDataBuffer.emplace_back(mQrDataSt2);
    // 序列化
    RobotDataSt outData(mQrDataBuffer);
    writeJson(outFilename, outData);
    // 反序列化
    RobotDataSt inData;
    readJson(outFilename, inData);
    inData.print();


    return 0;
}
2.CMakeLists.txt
cmake_minimum_required(VERSION 3.10.0)

project(jsonParser)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_CXX_STANDARD 14)

find_package(Boost REQUIRED COMPONENTS
        filesystem)

file(GLOB SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)

add_executable(${PROJECT_NAME} ${SRCS})

target_link_libraries(${PROJECT_NAME}
        ${Boost_LIBRARIES})

3.output.json
{
    "qrDataSt": [
        {
            "poseIndex": 1,
            "qrDeg": 0.0,
            "qrIndex": 110,
            "qrX": 955.0,
            "qrY": -939.167
        },
        {
            "poseIndex": 2,
            "qrDeg": 0.0,
            "qrIndex": 120,
            "qrX": 2149.88,
            "qrY": -344.329
        }
    ]
}

4.终端输出
/home/test/code/exercise/readJson/build/jsonParser
poseIndex=1
qrIndex=110
qrX=955
qrY=-939.167
qrDeg=0
poseIndex=2
qrIndex=120
qrX=2149.88
qrY=-344.329
qrDeg=0

Process finished with exit code 0
5.代码详解

观察output.json可知,文件存储着一个RobotDataSt类型数据,而RobotDataSt类型又包含两个QrDataSt类型的数据。
代码工作流程是先将定义好的数据写入到json文件,再读取打印至终端。其中读写利用c++输入输出流实现,关键的RobotDataSt类型数据序列化实质上利用to_json友元方法自动实现序列化,
同理,Json类型数据反序列化利用from_json友元方法实现。

三、问题

假设RobotDataSt还需新增一个CameraDataSt类型的数据结构,是否可以使用struct自定义一个类型直接添加到RobotDataSt中使用?

struct CameraDataSt {
    CameraType type;
    std::string username;
    std::string password;
};

答案是否定的,如果新增的数据结构不按照(1)宏使用规则定义是无法使用的。

四、拓展

如果使用(1)宏,构造一个不完整的Json数据进行反序列化,则输出将会抛出json.exception.out_of_range.403异常,因此可使用(2)宏,缺失的数据进行反序列化时将自动填充默认值。

五、官方链接

https://github.com/nlohmann/json
https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
;