C++ implementa un procesamiento amigable de datos Json

antecedentes

El cliente C/C++ necesita recibir y enviar datos en formato JSON al backend para la comunicación y la interacción de datos. C++ no tiene una interfaz preparada para procesar datos con formato JSON, y la referencia directa a bibliotecas de terceros aún no puede evitar el desmontaje y el empalme. Teniendo en cuenta que este proyecto tendrá que procesar una gran cantidad de datos JSON, no se pueden evitar las divisiones y empalmes repetidos. Por lo tanto, planeamos encapsular un conjunto de objetos de estructura C++ en datos JSON, y datos JSON para instalar directamente interfaces de objetos de estructura C++, similares a la serialización y deserialización comúnmente utilizadas en la transmisión de datos, para facilitar el procesamiento posterior de datos y mejorar la eficiencia del desarrollo.

diseño

Objetivo:

  1. Una instancia de objeto de estructura C++ se puede convertir en datos de cadena JSON a través de una interfaz simple, o una cadena de datos de cadena JSON se puede cargar y asignar a una instancia de objeto de estructura C++. Interfaz ideal: Json2Object(inJsonString, outStructObject), oObject2Json(inStructObject, outJsonString)
  2. Admite la conversión Json de tipos básicos integrados como bool, int, double, admite la conversión Json de estructuras personalizadas, admite la conversión Json de los tipos anteriores como matrices de elementos y admite la conversión Json de estructuras anidadas

Efecto:

Código de prueba de unidad primero

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");
}

lograr

Esta vez, solo nos enfocamos en cómo convertir entre estructuras y cadenas Json de una manera amigable, sin prestar atención a cómo convertir cadenas JSon con tipos de datos básicos. Ya existen muchas bibliotecas de terceros para ayudarnos a resolver este problema, como Cjson, jsoncpp, rapidjson, por lo que no es necesario reinventar la rueda.

Esta vez, elegimos JsonCPP como soporte de análisis JSON subyacente. Si desea reemplazarlo con otras bibliotecas de terceros, es relativamente fácil modificar el contenido incrustado correspondiente.

Nuestro objetivo es implementar dos interfaces:

  • Json2Object(inJsonString, outStructObject)

  • Object2Json(inStructObject, outJsonString)

Combinado con los tipos definidos por el propio JsonCPP, lo que necesitamos implementar más es:

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

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

Conversión de tipos de datos básicos

Para tipos de datos básicos como bool, int, double, string, etc., la implementación es relativamente simple:

/*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;
}

tipo de estructura de datos personalizada

Para un tipo de estructura personalizado, todo lo que debemos hacer es asegurarnos de que sus variables miembro puedan corresponder uno a uno con los nodos JSON y puedan coincidir para el llenado de datos.

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

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

Como en el ejemplo anterior, durante el proceso de conversión mutua, "boolValue" puede corresponder a la variable miembro denominada boolValue en el objeto DemoObjct, y "strValue" corresponde a la variable miembro denominada strValue en el objeto DemoObjct.

En circunstancias normales, para este escenario, solo podemos implementar funciones de procesamiento adicionales para que la estructura DemoObjct realice la conversión de datos. Debido a que las variables miembro definidas por diferentes declaraciones de estructura son diferentes, cada estructura debe implementarse por separado. Engorroso y no universal.

A partir de aquí, lo que tenemos que hacer es "ocultar" las funciones de conversión implementadas para la estructura de clases, y usar las características del propio lenguaje (plantillas de funciones, etc.) para que hagan estas cosas por nosotros.

  1. Declare una función miembro de conversión y, en la implementación de esta función miembro, permita que cada variable miembro lea o escriba valores de datos nativos JSON.
  2. El propósito de registrar variables miembro es permitir que la función miembro de conversión sepa qué variables miembro deben procesarse, y cada variable miembro corresponde a qué campo de nodo en los datos nativos JSON, para que coincida con la lectura y la escritura.
  3. Json2Object Cuando se llama a una llamada y función externas Object2Json, la función miembro de conversión se activa para completar o generar el contenido de la variable miembro.

Solo se necesitan tres pasos para poner un elefante en el refrigerador. Veamos cómo hacer estos tres pasos.

manejo de variables miembro

  • Teniendo en cuenta que el tipo y la cantidad de variables miembro de cada estructura son incontrolables, y cada variable miembro debe tratarse como un valor l ( Json2Objecttiempo), no puede manejarse simplemente mediante la enumeración de matrices, y la característica de C++ 11 - variable puede Se utilizará la plantilla de parámetros, atravesando cada variable miembro de adentro hacia afuera.
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...);
    }
}
  • Las variables miembro tienen una relación correspondiente con el campo clave del nodo nativo JSON.Inicialmente, considere el nombre de la variable miembro como el nombre clave del nodo correspondiente en JSON. Esto se puede lograr a través de la función definida por la macro, y el contenido de la variable miembro declarada y registrada se divide en una lista de nombres clave como una cadena.
#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__);
}

registro de variable miembro

Por ejemplo DemoObjct, esta estructura de clase, agrega JSONCONVERT2OBJECT_MEMEBER_REGISTERy trae la declaración de registro de las variables miembro:

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

Equivalente a:

   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);
        }      
    };

La coincidencia de plantillas evita errores de compilación

Hasta ahora, se ha implementado la funcionalidad principal. Si la clase de estructura de destino no agrega el registro de declaración de conversión JSON, el uso externo de la Json2Objectinterfaz provocará un error de compilación, lo que indica ParseHelpImplque no se puede encontrar la definición de declaración de esta función miembro..., podemos usarla enable_ifpara proporcionar la estructura que no declara la función macro registro provincia.

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;
}

Cambio de nombre de clave de coincidencia de variable miembro

La implementación actual es usar el nombre de la variable miembro como el nombre clave en la cadena JSON, para un procesamiento flexible, y agregar una macro para volver a declarar la clave correspondiente a la cadena JSON en la variable miembro de la estructura, por ejemplo:

    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!");

Implementación de Object2Json

Las operaciones mencionadas anteriormente se proporcionan principalmente para implementar Json2Objectinterfaces, y la conversión de un objeto de estructura a Json también es una operación similar, que no se describirá aquí. Para obtener más información, consulte el código fuente.

Reflejos

  • Simplifique el procesamiento de datos JSON por C++ y proteja las operaciones de división y procesamiento de datos JSON;
  • Proporcione una interfaz simple, cambie libremente de estructura a cadena JSON, cadena JSON a estructura

código fuente



#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;
}

Supongo que te gusta

Origin blog.csdn.net/weixin_55305220/article/details/123578060
Recomendado
Clasificación