C++ реализует дружественную обработку данных Json.

задний план

Клиент C/C++ должен получать и отправлять данные в формате JSON на серверную часть для связи и взаимодействия с данными. C++ не имеет готового интерфейса для обработки данных в формате JSON, а прямое обращение к сторонним библиотекам все равно не может избежать дизассемблирования и сплайсинга. Учитывая, что в этом проекте будет обрабатываться большой объем JSON-данных, повторного разделения и сплайсинга не избежать. Поэтому мы планируем инкапсулировать набор объектов структуры C++ в данные JSON, а данные JSON — для прямой установки интерфейсов объектов структуры C++, аналогичных сериализации и десериализации, обычно используемых при передаче данных, чтобы облегчить последующую обработку данных и повысить эффективность разработки.

дизайн

Цель:

  1. Экземпляр объекта структуры C++ можно преобразовать в строковые данные JSON через простой интерфейс, или строку строковых данных JSON можно загрузить и назначить экземпляру объекта структуры C++. Идеальный интерфейс: Json2Object(inJsonString, outStructObject), илиObject2Json(inStructObject, outJsonString)
  2. Поддерживает преобразование Json встроенных базовых типов, таких как bool, int, double, поддерживает преобразование Json пользовательских структур, поддерживает преобразование Json вышеуказанных типов в виде массивов элементов и поддерживает преобразование Json вложенных структур.

Эффект:

Сначала код модульного теста

TEST_CASE("解析结构体数组到JSON串", "[json]")
{
    struct DemoChildrenObject
    {
        bool boolValue;
        int intValue;
        std::string strValue;
        /*JSON相互转换成员变量声明(必需)*/
        JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue)
    };

    struct DemoObjct
    {
        bool boolValue;
        int intValue;
        std::string strValue;
        /*嵌套的支持JSON转换的结构体成员变量,数组形式*/
        std::vector< DemoChildrenObject> children;
        
        /*JSON相互转换成员变量声明(必需)*/
        JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue, children)
    };

    DemoObjct demoObj;
    /*开始对demoObj对象的成员变量进行赋值*/
    demoObj.boolValue = true;
    demoObj.intValue = 321;
    demoObj.strValue = "hello worLd";

    DemoChildrenObject child1;
    child1.boolValue = true;
    child1.intValue = 1000;
    child1.strValue = "hello worLd child1";

    DemoChildrenObject child2;
    child2.boolValue = true;
    child2.intValue = 30005;
    child2.strValue = "hello worLd child2";

    demoObj.children.push_back(child1);
    demoObj.children.push_back(child2);
    /*结束对demoObj对象的成员变量的赋值*/

    std::string jsonStr;
    /*关键转换函数*/
    REQUIRE(Object2Json(jsonStr, demoObj)); 
    std::cout << "returned json format: " << jsonStr << std::endl;

    /*打印的内容如下:
    returned json format: {
       "boolValue" : true,
       "children" : [
          {
             "boolValue" : true,
             "intValue" : 1000,
             "strValue" : "hello worLd child1"
          },
          {
             "boolValue" : true,
             "intValue" : 30005,
             "strValue" : "hello worLd child2"
          }
       ],
       "intValue" : 321,
       "strValue" : "hello worLd"
    }
    */
    
    DemoObjct demoObj2;
    /*关键转换函数*/
    REQUIRE(Json2Object(demoObj2, jsonStr));

    /*校验转换后的结构体变量中各成员变量的内容是否如预期*/
    REQUIRE(demoObj2.boolValue == true);
    REQUIRE(demoObj2.intValue == 321);
    REQUIRE(demoObj2.strValue == "hello worLd");

    REQUIRE(demoObj2.children.size() == 2);

    REQUIRE(demoObj.children[0].boolValue == true);
    REQUIRE(demoObj.children[0].intValue == 1000);
    REQUIRE(demoObj.children[0].strValue == "hello worLd child1");

    REQUIRE(demoObj.children[1].boolValue == true);
    REQUIRE(demoObj.children[1].intValue == 30005);
    REQUIRE(demoObj.children[1].strValue == "hello worLd child2");
}

выполнить

На этот раз мы сосредоточимся только на том, как конвертировать между структурами и строками Json удобным способом, не обращая внимания на то, как конвертировать строки JSON с базовыми типами данных. Уже существует множество сторонних библиотек, помогающих нам решить эту проблему, таких как Cjson, jsoncpp, rapidjson, поэтому нет необходимости изобретать велосипед.

На этот раз мы выбрали JsonCPP в качестве базовой поддержки синтаксического анализа JSON.Если вы хотите заменить его другими сторонними библиотеками, относительно легко изменить соответствующий встроенный контент.

Наша цель — реализовать два интерфейса:

  • Json2Object(inJsonString, outStructObject)

  • Object2Json(inStructObject, outJsonString)

В сочетании с типами, определенными самим JsonCPP, нам нужно реализовать следующее:

  • Json2Object(const Json::Value& jsonTypeValue, outStructObject)

  • Object2Json(inStructObject, const std::string& key, Json::Value& jsonTypeValue)

Преобразование базового типа данных

Для базовых типов данных, таких как bool, int, double, string и т. д., реализация относительно проста:

/*int 类型支持*/
static bool Json2Object(int& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isInt()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asInt();
        return true;
    }
}

static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const int& value)
{
    jsonTypeValue[key] = value;
    return true;
}

/*std::string 字符串类型支持*/
static bool Json2Object(std::string& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isString()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asString();
        return true;
    }
}

static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const std::string& value)
{
    jsonTypeValue[key] = value;
    return true;
}

пользовательский тип структуры данных

Для пользовательского типа структуры все, что нам нужно сделать, — это убедиться, что его переменные-члены могут однозначно соответствовать узлам JSON и могут совпадать для заполнения данных.

   /*Json字符串:
   {
   "boolValue" : true,
   "intValue" : 1234,
   "strValue" : "demo object!"
   }*/

   struct DemoObjct
    {
        bool boolValue;
        int intValue;
        std::string strValue;
    };

Как и в приведенном выше примере, в процессе взаимного преобразования "boolValue" может соответствовать переменной-члену с именем boolValue в объекте DemoObjct, а "strValue" соответствует переменной-члену с именем strValue в объекте DemoObjct.

При нормальных обстоятельствах для этого сценария мы можем реализовать только дополнительные функции обработки для структуры DemoObjct для выполнения преобразования данных.Поскольку переменные-члены, определенные объявлениями разных структур, различаются, каждую структуру необходимо реализовывать отдельно.Громоздко и не универсально.

Отсюда нам нужно «скрыть» функции преобразования, реализованные для структуры класса, и использовать характеристики самого языка (шаблоны функций и т. д.), чтобы позволить им делать эти вещи за нас.

  1. Объявите функцию-член преобразования и в этой реализации функции-члена разрешите каждой переменной-члену читать или записывать значения из собственных данных JSON.
  2. Цель регистрации переменных-членов — сообщить функции-члену преобразования, какие переменные-члены необходимо обработать, и каждая переменная-член соответствует какому полю узла в собственных данных JSON, чтобы согласовать чтение и запись.
  3. Когда вызывается внешний вызов Json2Object и Object2Jsonфункция, функция-член преобразования запускается для заполнения или вывода содержимого переменной-члена.

Чтобы поместить слона в холодильник, нужно всего три шага Давайте посмотрим, как сделать эти три шага.

обработка переменных-членов

  • Учитывая, что тип и количество переменных-членов каждой структуры неконтролируемы, и каждая переменная-член должна рассматриваться как lvalue ( Json2Objectвремя), она не может быть просто обработана перечислением массива, а особенность C++11 - переменная может использовать шаблон параметра, просматривая каждую переменную-член изнутри
template <typename T>
static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg)
{
    const auto key = names[index];
    if (!jsonTypeValue.isMember(key) || Json2Object(arg, jsonTypeValue[key])) {
        return true;
    } else {
        return false;
    }
}

template <typename T, typename... Args>
static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg, Args&... args)
{
    if (!JsonParse(names, index, jsonTypeValue, arg)) {
        return false;
    } else {
        return JsonParse(names, index + 1, jsonTypeValue, args...);
    }
}
  • Переменные-члены имеют соответствующую связь с ключевым полем собственного узла JSON.Первоначально рассматривайте имя переменной-члена как имя ключа соответствующего узла в JSON. Этого можно достичь с помощью функции, определенной макросом, а содержимое объявленной и зарегистрированной переменной-члена разбивается на список имен ключей в виде строки.
#define JSONCONVERT2OBJECT_MEMEBER_REGISTER(...)  \
bool ParseHelpImpl(const Json::Value& jsonTypeValue)
{
    std::vector<std::string> names = Member2KeyParseWithStr(#__VA_ARGS__);
    return JsonParse(names, 0, jsonTypeValue, __VA_ARGS__);
}

регистрация переменных-членов

Например DemoObjct, эта структура класса, добавьте JSONCONVERT2OBJECT_MEMEBER_REGISTERи принесите регистрационное объявление переменных-членов:

   struct DemoObjct
    {
        bool boolValue;
        int intValue;
        std::string strValue;
       
       JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue)
           
    };

Эквивалентно:

   struct DemoObjct
    {
        bool boolValue;
        int intValue;
        std::string strValue;
       
       bool ParseHelpImpl(const Json::Value& jsonTypeValue, 
                          std::vector<std::string> &names)
        {
            names = Member2KeyParseWithStr("boolValue, intValue, strValue");
            //names 得到 ["boolValue","intValue", "strValue"]
            //然后带着这些key逐一从Json中取值赋值到成员变量中
            return JsonParse(names, 0, jsonTypeValue, boolValue, intValue, strValue);
        }      
    };

Сопоставление шаблонов предотвращает ошибки компиляции

На данный момент реализован основной функционал. Если класс целевой структуры не добавляет регистрацию объявления преобразования JSON, внешнее использование Json2Objectинтерфейса вызовет ошибку компиляции, указывающую ParseHelpImpl, что определение объявления этой функции-члена не может быть найдено..., мы можем использовать enable_ifего для предоставления структура, которая не объявляет функцию регистрации макроса провинция.

template <typename TClass, typename enable_if<HasConverFunction<TClass>::has, int>::type = 0>
static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue)
{
    std::vector<std::string> names = PreGetCustomMemberNameIfExists(aimObj);
    return aimObj.JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeValue, names);
}

template <typename TClass, typename  enable_if<!HasConverFunction<TClass>::has, int>::type = 0>
static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue)
{
    return false;
}

Переименование переменной-члена, соответствующее переименованию ключа

Текущая реализация заключается в использовании имени переменной-члена в качестве имени ключа в строке JSON для гибкой обработки и добавлении макроса для повторного объявления ключа, соответствующего строке JSON, в переменной-члене структуры, например:

    struct DemoObjct
    {
        bool boolValue;
        int intValue;
        std::string strValue;

        JSONCONVERT2OBJECT_MEMEBER_REGISTER(boolValue, intValue, strValue)
        /*重新声明成员变量对应到JSON串的key,注意顺序一致*/
        JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER("bValue", "iValue", "sValue")
    };

    DemoObjct demoObj;
    /*boolValue <--> bValue; intValue <--> iValue; ...*/
    REQUIRE(Json2Object(demoObj, std::string("{\"bValue\":true, \"iValue\":1234, \"sValue\":\"demo object!\"}")));
    REQUIRE(demoObj.boolValue == true);
    REQUIRE(demoObj.intValue == 1234);
    REQUIRE(demoObj.strValue == "demo object!");

Реализация Object2Json

Упомянутые выше операции в основном предусмотрены для реализации Json2Objectинтерфейсов, и преобразование из объекта структуры в Json также является аналогичной операцией, которая не будет здесь описываться, подробнее см. в исходном коде.

Основные моменты

  • Упростите обработку данных JSON с помощью C++ и защитите операции разделения и обработки данных JSON;
  • Предоставьте простой интерфейс, свободно переключайтесь со структуры на строку JSON, со строки JSON на структуру

исходный код



#include "json/json.h"
#include <string>
#include <vector>
#include <initializer_list>

#define JSONCONVERT2OBJECT_MEMEBER_REGISTER(...)  \
bool JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE(const Json::Value& jsonTypeValue, std::vector<std::string> &names) \
{     \
    if(names.size() <= 0)  { \
        names = Member2KeyParseWithStr(#__VA_ARGS__); \
    }   \
    return JsonParse(names, 0, jsonTypeValue, __VA_ARGS__); \
}   \
bool OBJECTCONVERT2JSON_MEMEBER_REGISTER_RESERVERD_IMPLE(Json::Value& jsonTypeValue, std::vector<std::string> &names) const \
{     \
    if(names.size() <= 0)  { \
        names = Member2KeyParseWithStr(#__VA_ARGS__); \
    }   \
    return ParseJson(names, 0, jsonTypeValue, __VA_ARGS__); \
}   \


#define JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER(...)  \
std::vector<std::string> JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER_RESERVERD_IMPLE() const \
{     \
    return Member2KeyParseWithMultiParam({ __VA_ARGS__ }); \
}

namespace JSON
{
template <bool, class TYPE = void>
struct enable_if
{
};

template <class TYPE>
struct enable_if<true, TYPE>
{
    typedef TYPE type;
};
} //JSON

template <typename T>
struct HasConverFunction
{
    template <typename TT>
    static char func(decltype(&TT::JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE)); //@1
    
    template <typename TT>
    static int func(...); //@2

    const static bool has = (sizeof(func<T>(NULL)) == sizeof(char));

    template <typename TT>
    static char func2(decltype(&TT::JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER_RESERVERD_IMPLE)); //@1
    template <typename TT>
    static int func2(...); //@2
    const static bool has2 = (sizeof(func2<T>(NULL)) == sizeof(char));
};

static std::vector<std::string> Member2KeyParseWithMultiParam(std::initializer_list<std::string> il)
{
    std::vector<std::string> result;
    for (auto it = il.begin(); it != il.end(); it++) {
        result.push_back(*it);
    }
    return result;
}

inline static std::string NormalStringTrim(std::string const& str)
{
    static char const* whitespaceChars = "\n\r\t ";
    std::string::size_type start = str.find_first_not_of(whitespaceChars);
    std::string::size_type end = str.find_last_not_of(whitespaceChars);
    return start != std::string::npos ? str.substr(start, 1 + end - start) : std::string();
}

inline static std::vector<std::string> NormalStringSplit(std::string str, char splitElem)
{
    std::vector<std::string> strs;
    std::string::size_type pos1, pos2;
    pos2 = str.find(splitElem);
    pos1 = 0;
    while (std::string::npos != pos2) {
        strs.push_back(str.substr(pos1, pos2 - pos1));
        pos1 = pos2 + 1;
        pos2 = str.find(splitElem, pos1);
    }
    strs.push_back(str.substr(pos1));
    return strs;
}

static std::vector<std::string> Member2KeyParseWithStr(const std::string& values)
{
    std::vector<std::string> result;
    auto enumValues = NormalStringSplit(values, ',');
    result.reserve(enumValues.size());
    for (auto const& enumValue : enumValues) {
        result.push_back(NormalStringTrim(enumValue));
    }
    return result;
}

//

static bool Json2Object(bool& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isBool()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asBool();
        return true;
    }
}
static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, bool value)
{
    jsonTypeValue[key] = value;
    return true;
}

static bool Json2Object(int& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isInt()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asInt();
        return true;
    }
}
static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const int& value)
{
    jsonTypeValue[key] = value;
    return true;
}

static bool Json2Object(unsigned int& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isUInt()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asUInt();
        return true;
    }
}
static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const unsigned int& value)
{
    jsonTypeValue[key] = value;
    return true;
}

static bool Json2Object(double& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isDouble()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asDouble();
        return true;
    }
}
static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const double& value)
{
    jsonTypeValue[key] = value;
    return true;
}

static bool Json2Object(std::string& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isString()) {
        return false;
    } else {
        aimObj = jsonTypeValue.asString();
        return true;
    }
}
static bool Object2Json(Json::Value& jsonTypeValue, const std::string& key, const std::string& value)
{
    jsonTypeValue[key] = value;
    return true;
}

template <typename TClass, typename JSON::enable_if<HasConverFunction<TClass>::has2, int>::type = 0>
static inline std::vector<std::string> PreGetCustomMemberNameIfExists(const TClass& aimObj)
{
    return aimObj.JSONCONVERT2OBJECT_MEMEBER_RENAME_REGISTER_RESERVERD_IMPLE();
}

template <typename TClass, typename JSON::enable_if<!HasConverFunction<TClass>::has2, int>::type = 0>
static inline std::vector<std::string> PreGetCustomMemberNameIfExists(const TClass& aimObj)
{
    return std::vector<std::string>();
}

template <typename TClass, typename JSON::enable_if<HasConverFunction<TClass>::has, int>::type = 0>
static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue)
{
    std::vector<std::string> names = PreGetCustomMemberNameIfExists(aimObj);
    return aimObj.JSONCONVERT2OBJECT_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeValue, names);
}

template <typename TClass, typename JSON::enable_if<!HasConverFunction<TClass>::has, int>::type = 0>
static inline bool Json2Object(TClass& aimObj, const Json::Value& jsonTypeValue)
{
    return false;
}

template <typename T>
static bool Json2Object(std::vector<T>& aimObj, const Json::Value& jsonTypeValue)
{
    if (jsonTypeValue.isNull() || !jsonTypeValue.isArray()) {
        return false;
    } else {
        aimObj.clear();
        bool result(true);
        for (int i = 0; i < jsonTypeValue.size(); ++i) {
            T item;
            if (!Json2Object(item, jsonTypeValue[i])) {
                result = false;
            }
            aimObj.push_back(item);
        }
        return result;
    }
}

template <typename T>
static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg)
{
    const auto key = names[index];
    if (!jsonTypeValue.isMember(key) || Json2Object(arg, jsonTypeValue[key])) {
        return true;
    } else {
        return false;
    }
}

template <typename T, typename... Args>
static bool JsonParse(const std::vector<std::string>& names, int index, const Json::Value& jsonTypeValue, T& arg, Args&... args)
{
    if (!JsonParse(names, index, jsonTypeValue, arg)) {
        return false;
    } else {
        return JsonParse(names, index + 1, jsonTypeValue, args...);
    }
}

/** Provider interface*/
template<typename TClass>
bool Json2Object(TClass& aimObj, const std::string& jsonTypeStr)
{
    Json::Reader reader;
    Json::Value root;

    if (!reader.parse(jsonTypeStr, root) || root.isNull()) {
        return false;
    }
    return Json2Object(aimObj, root);
}

static bool GetJsonRootObject(Json::Value& root, const std::string& jsonTypeStr)
{
    Json::Reader reader;
    if (!reader.parse(jsonTypeStr, root)) {
        return false;
    }
    return true;
}



template <typename TClass, typename JSON::enable_if<HasConverFunction<TClass>::has, int>::type = 0>
static inline bool Object2Json(Json::Value& jsonTypeOutValue, const std::string& key, const TClass& objValue)
{
    std::vector<std::string> names = PreGetCustomMemberNameIfExists(objValue);
    if (key.empty()) {
        return objValue.OBJECTCONVERT2JSON_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeOutValue, names);
    } else {
        Json::Value jsonTypeNewValue;
        const bool result = objValue.OBJECTCONVERT2JSON_MEMEBER_REGISTER_RESERVERD_IMPLE(jsonTypeNewValue, names);
        if (result) {
            jsonTypeOutValue[key] = jsonTypeNewValue;
        }
        return result;
    }
}

template <typename TClass, typename JSON::enable_if<!HasConverFunction<TClass>::has, int>::type = 0>
static inline bool Object2Json(Json::Value& jsonTypeOutValue, const std::string& key, const TClass& objValue)
{
    return false;
}

template <typename T>
static bool Object2Json(Json::Value& jsonTypeOutValue, const std::string& key, const std::vector<T>& objValue)
{
    bool result(true);
    for (int i = 0; i < objValue.size(); ++i) {
        Json::Value item;
        if (!Object2Json(item, "", objValue[i])) {
            result = false;
        } else {
            if (key.empty()) jsonTypeOutValue.append(item);
            else jsonTypeOutValue[key].append(item);
        }
     }
    return result;
}

template <typename T>
static bool ParseJson(const std::vector<std::string>& names, int index, Json::Value& jsonTypeValue, const T& arg)
{
    if (names.size() > index) {
        const std::string key = names[index];
        return Object2Json(jsonTypeValue, key, arg);
    } else {
        return false;
    }
}

template <typename T, typename... Args>
static bool ParseJson(const std::vector<std::string>& names, int index, Json::Value& jsonTypeValue, T& arg, Args&... args)
{
    if (names.size() - (index + 0) != 1 + sizeof...(Args)) {
        return false;
    }
    const std::string key = names[index];
    Object2Json(jsonTypeValue, key, arg);

    return ParseJson(names, index + 1, jsonTypeValue, args...);
}

/** Provider interface*/
template<typename T>
bool Object2Json(std::string& jsonTypeStr, const T& obj)
{
    //std::function<Json::Value()>placehoder = [&]()->Json::Value { return Json::Value(); };
    //auto func = [&](std::function<Json::Value()>f) { return f(); };
    //Json::Value val = func(placehoder);

    Json::StyledWriter writer;
    Json::Value root;
    const bool result = Object2Json(root, "", obj);
    if (result) {
        jsonTypeStr = writer.write(root);
    }
    return result;
}

рекомендация

отblog.csdn.net/weixin_55305220/article/details/123578060