Background:

Converting objects to and from JSON strings is a common problem in projects. In most programs, this problem is usually solved by a single function. In c++, however, we need to manually initialize/serialize the json library field by field. Wouldn’t it be convenient to have a function that could code the unmarshal /marshal object one line at a time? This article is based on the JSONCPP library, the design of such a function can support this function, below enter the topic ~

Design ideas

In the case of Unmarshal, our final function was to build two template functions like this:

A direct deserialization object from the Josn of String and a JSON object from the JSONCPp library.

template<typename T> bool Unmarshal(T& obj,const string& json_str);
template<typename T> bool Unmarshal(T& obj,const Json::Value& json_obj_root);
Copy the code

Json is a self-recursive structure, so in the design, should also be a recursive way to solve the complex combination of classes, we can simply divide the variables in the program into the following categories:

In this way, we only need to Unmarshal these scenarios, and the overall Unmarshal will be implemented. The template design class diagram should correspond to our classification:

Note the following points in the implementation:

1. The Unmarshal templates for each category are exclusive, meaning that primitive types can only match templates that resolve primitive types at compile time

2. Due to 1’s guarantee and the fact that these template functions are named Unmarshal, they can nest calls to each other.

3. Pointers and native arrays will involve space allocation and length detection, which will make the situation complicated. The scope supported by this design does not include Pointers and native arrays.

Matches the Unmarshal template of the base type

// Can parse only a set of templates of the basic type int long bool float double string
Template 
      
        bool Unmarshal(int& obj,const Json::Value &root); The compiler failed because * could not infer the type of T at compile time. So set the template type list to template 
       
         (Nontype * Parameters) where int is meaningless */
       
      
template <int = 0> 
inline bool Unmarshal(int& obj,const Json::Value &root){
    if(! root.isIntegral())
        return false;
    obj = root.asInt(a);return true;
}

template <int = 0>
inline bool Unmarshal(long& obj,const Json::Value &root)

.....
Copy the code

Matches the Unmarshal template for STL containers/other third-party libraries

Vector 
      
        map
       
         map
        
          map
        ,t>
       ,t>
      
//vector
template <typename T>
bool Unmarshal(vector<T>& obj,const Json::Value& root){
    if(! root.isArray())
        return false;
    obj.clear(a);bool ret = true;
    for(int i=0; i<root.size(a); ++i){ T tmp;// Type T contains the T() constructor
        if(!Unmarshal(tmp,root[i])) // Call Unmarshal recursively
            ret = false;
        obj.push_back(tmp);
    }
    return ret;
}

//map key:string
template <typename T> 
bool Unmarshal(map<string,T>& obj,const Json::Value& root){... }//map key:long
template <typename T> 
bool Unmarshal(map<long,T>& obj,const Json::Value& root){... }//map key:int
template <typename T> 
bool Unmarshal(map<int,T>& obj,const Json::Value& root){... }Copy the code

Matches the Unmarshal template of a custom struct/class

Implementing a set of structs/classes that can only match our own requires that the objects we define have some special flags so that they can be recognized by template functions. If an object contains a public unmarshal method, we will know that it is our own class, and then we will call the specific template function. This uses a C++ syntax **SFINAE(**Substitution Failure Is Not An Error) and the STD library enable_if

Let’s start with a brief implementation of enable_if in the C++ STD library:

Version 1 has an empty enable_IF structure
template <bool.class _Tp = void> 
struct enable_if {};

Version 2 is a special implementation of version 1 where the first argument is true. This version contains type type enable_if
template <class _Tp> 
struct enable_if<true, _Tp> {typedef_Tp type; };int main(a){
    enable_if<true.int>::type a = 3; // Match version 2, equivalent to int a = 3
    enable_if<false.int>::type b = 3; // Match version 1. Enable_if {} has no type of type, triggering a compilation error
}
Copy the code

**SFINAE rules that a failed match is not an error, ** If the compiler raises an error while matching a template, the compiler should try the next match, not abort. Using this rule and enable_IF, parse our own struct/class Umarshal template design as follows:

// Check if a class contains non-static, non-overloaded unmarshal methods
template<typename T>
struct TestUnmarshalFunc {

    / / version 1
    template<typename TT>
    static char func(decltype(&TT::unmarshal));

    / / version 2
    template<typename TT>
    static int func(...).;

    /* * Func 
      
       (NULL) will match version 1 if there is no unmarshal method. Due to the SFINAE rule,func
       
        (NULL) can only match version 2 * func. Instead, the has variable is true */
       
      
    const static bool has = (sizeof(func<T>(NULL)) = =sizeof(char));
};


Unmarshal is called if the object itself contains the unmarshal method. Otherwise, this version of Unamrshal will be skipped due to SFINAE criteria
template <typename T,typename enable_if<TestUnmarshalFunc<T>::has,int>::type = 0>
inline bool Unmarshal(T& obj,const Json::Value &root){
    return obj.unmarshal(root);
}
Copy the code

Ok, so we have three basic types of Umarshal functions designed, where any T type will eventually match one of the three when Unmarshal is called. Unmarshal can be used to pack a version of json as string:

template <typename T>
bool Unmarshal(T& obj,const string &s){
    Json::Reader reader;
    Json::Value root;
    if(! reader.parse(s,root))
        return false;
    return Unmarshal(obj,root);
}
Copy the code

Let’s look at how to implement the unmarshal function in a custom class:

// Assuming we have a People object with three fields to deserialize, we might need to write unmarshal ourselves as follows
struct People{
    bool sex;
    int age;
    string name;

    // Try to parse each field, return true only if all fields are properly parsed
    bool unmarshal(const Json::Value &root){
        bool ret = true;
        if(! Json::Unmarshal(sex,root["sex"])){   
            ret = false;                            
        }
        if(! Json::Unmarshal(age,root["age"])){
            ret = false;
        }
        if(! Json::Unmarshal(name,root["name"])){
            ret = false;
        }
        returnret; }};Copy the code

Obviously, if you have a lot of fields, it’s cumbersome, and the code format is very similar when parsing each field, is there a macro that can be generated automatically? The answer is yes. Talk is cheap,show me the code!

struct People{
    bool sex;
    int age;
    string name;
    
    // Unmarshal is automatically generated in the class by passing in the fields to be serialized with the JSON_HELP macro
    JSON_HELP(sex,age,name) 
};

// JSON_HELP is a variable macro
#define JSON_HELP(...)          \
    UNMARSHAL_OBJ(__VA_ARGS__)  \    // The unmarshal function is generated
    MARSHAL_OBJ(__VA_ARGS__)


/* * The FOR_EACH macro in UNMARSHAL_OBJ takes the first argument to a function and the second argument to a list. The FOR_EACH macro in UNMARSHAL_OBJ takes the first argument to a function and the second argument to a list.  * if(! Json::Unmarshal(field_1,root["field_1"])){ * ret = false; * } * if(! Json::Unmarshal(field_2,root["field_2"])){ * ret = false; *} *... . * /
#defineUNMARSHAL_OBJ(...) \ bool unmarshal(const Json::Value& root){ \ bool ret = true; \ FOR_EACH(__unmarshal_obj_each_field__,__VA_ARGS__) \ return ret; The \}

#define __unmarshal_obj_each_field__(field)         \
        if(! Json::Unmarshal(field,root[#field])){ \ ret = false; The \}



//###### FOR_EACH implements #######
// Function: pass in a function func and a list, and apply func to each element of the list
#define FOR_EACH(func,...) \
    MACRO_CAT(__func_,COUNT(__VA_ARGS__))(func,__VA_ARGS__)

* if __VA_ARGS__ has three variables a,b, and c, then the macro expansion will be: * __func_3 (func, a, b, c), and __func_3 __func_2 __func_1... * // / macro expansion implementation pseudo-loop /* * __func_3(func,a,b,c) concrete expansion process: * first: __func_1(func,a) __func_2(func,b,c) * second: Func (a) __func_1(func,b) __func_1(func,c) * third time: func(a) func(b) func(c) * finally calls the passed func function */ on each of a,b, and c
#define __func_1(func,member)     func(member);
#define __func_2(func,member,...) __func_1(func,member)  __func_1(func,__VA_ARGS__)
#define __func_3(func,member,...) __func_1(func,member)  __func_2(func,__VA_ARGS__)
#define __func_4(func,member,...) __func_1(func,member)  __func_3(func,__VA_ARGS__)
#define __func_5(func,member,...) __func_1(func,member)  __func_4(func,__VA_ARGS__). .//###### COUNT macro implementation #######
Eg: COUNT(a,b,c) returns 3
#define COUNT(...) __count__(0, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __count__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N###### MACRO_CAT macro implementation #######// Function: connect two tokens (macros) together
#define MACRO_CAT(a,b)  __macro_cat__(a,b)
#define __macro_cat__(a,b)  a##b
Copy the code

test

Now that we have the Umarshal and Marshal functions written, let’s test them:

Scenario: There is a map object that stores information about teachers, and each teacher also stores information about students taught by TA. The data structure is defined as follows:

struct Student {
    long    id;
    bool    sex;
    double  score;
    string  name;

    JSON_HELP(id,sex,score,name)
};
struct Teacher {
    string          name;
    int             subject;
    vector<Student> stus;

    JSON_HELP(name,subject,stus)
};

map<string,Teacher> tchs;  // Objects that need to be serialized and deserialized
Copy the code

Test code:

// json corresponding to the structure map
      ,teacher>
string ori = R"( { "Tea_1": { "name": "Tea_1", "subject": 3, "stus": [ { "id": 201721020126, "sex": false, "score": 80, "name": "Stu.a" }, { "id": 201101101537, "sex": true, "score": 0, "name": "Stu.b" } ] }, "Tea_2": { "name": "Tea_2", "subject": 1, "stus": [ { "id": 201521020128, "sex": true, "score": 59, "name": "Stu.c" } ] } } )";

int main(a) {
    map<string,Teacher> tchs;

    // Deserialize objects from JSON strings
    bool ret = Json::Unmarshal(tchs,ori);
    if(! ret){ cout<<"Antisequence failure"<<endl;
        return 0;
    }else{
        cout<<"Anti-sequence success"<<endl;
    }

    // Serialize the object to a JSON string
    cout<<"Output object serialized JSON :"<<endl;
    string obj2json;
    Json::Marshal(tchs,obj2json);
    cout<<obj2json;
}

//##### The command output is #####Unsequence successful output of serialized object JSON: {"Tea_1": {"name":"Tea_1"."stus": [{"id":201721020126."name":"Stu.a"."score":80.0."sex":false}, {"id":201101101537."name":"Stu.b"."score":0.0."sex":true}]."subject":3},"Tea_2": {"name":"Tea_2"."stus": [{"id":201521020128."name":"Stu.c"."score":59.0."sex":true}]."subject":1}}
Copy the code

A complete example address: git.xiaojukeji.com/sunriseyang…

Author: Yang Xin

  • If you register with Didi Cloud now, you will get 10,000 yuan in red envelopes
  • Special offer in August, 1C2G1M cloud server 9.9 yuan/month limited time grab
  • Didi Cloud messenger exclusive special benefits, including annual cloud server as low as 68 yuan/year
  • Enter master code [7886] to get a 10% discount for all GPU products