Click here to Skip to main content
Click here to Skip to main content

Tagged as

Go to top

A fast JSON parser/loader for your C++ project

, 12 Aug 2014
Rate this:
Please Sign up or sign in to vote.
An easy and quick way to load any JSON file into C++ projects

Introduction

Nowadays, Json structure has become the most used to in web apps to interchange data. This is because Json is the most simplest and human readable format, so is more friendly to use. On C++, the most common use of Json format is to load configurations files like xml format and there many applications that can export json format, so you can take advantage of it to import easy the data in your c++ projects.

1. Common ways to parse your json in C++

There are many libs that loads your json struct for your c++ projects. From many of them, I just want to mention jsoncpp library because is the one I started for one of my projects. With this,  we'll compare the steps to achieve your struct loaded into your program.

For example, If you have a file called sample.json with the following structure,

// Configuration options
{
    // Default encoding for text
    "encoding" : "UTF-8",
    
    // Plug-ins loaded at start-up
    "plug-ins" : [
        "python",
        "c++",
        "ruby"
        ],
        
    // Tab indent size
    "indent" : { "length" : 3, "use_space": true }
}
List 1.1

And we put the example code from json library to operate after succesfully loaded file,

Json::Value root;   // will contains the root value after parsing.
Json::Reader reader;
bool parsingSuccessful = reader.parse( config_doc, root );
if ( !parsingSuccessful )
{
    // report to the user the failure and their locations in the document.
    std::cout  << "Failed to parse configuration\n"
               << reader.getFormattedErrorMessages();
    return;
}

// Get the value of the member of root named 'encoding', return 'UTF-8' if there is no
// such member.
std::string encoding = root.get("encoding", "UTF-8" ).asString();
// Get the value of the member of root named 'encoding', return a 'null' value if
// there is no such member.
const Json::Value plugins = root["plug-ins"];
for ( int index = 0; index < plugins.size(); ++index )  // Iterates over the sequence elements.
   loadPlugIn( plugins[index].asString() );
   
setIndentLength( root["indent"].get("length", 3).asInt() );
setIndentUseSpace( root["indent"].get("use_space", true).asBool() );

// ...
// At application shutdown to make the new configuration document:
// Since Json::Value has implicit constructor for all value types, it is not
// necessary to explicitly construct the Json::Value object:
root["encoding"] = getCurrentEncoding();
root["indent"]["length"] = getCurrentIndentLength();
root["indent"]["use_space"] = getCurrentIndentUseSpace();

Json::StyledWriter writer;
// Make a new JSON document for the configuration. Preserve original comments.
std::string outputConfig = writer.write( root );

// You can also use streams.  This will put the contents of any JSON
// stream at a particular sub-value, if you'd like.
std::cin >> root["subtree"];

// And you can write to a stream, using the StyledWriter automatically.
std::cout << root;
List 1.2

As we see on the code of the list 1.2, we need to know every variable type to load the structure in the right way, so we can deduce that if the json structure increase the lines of code will increase too and imagine to have tonnes of json files for your project with thousands of variables to consider. Unless you have an enterprise with a employees, you will spend lot of time on productivity !!!

But keep calm. I've developed a a quick way to declare the interface with c++ and json code. My method consist doing the following steps,

  • Declare the data like C-structure style.
  • Parse and load Json file into C-structure.

2. Declare our interface data in C structure

As we can see, Json structure have a similar as an old C-structure type. For example, for the structure as we had seen in the list 1.1, we could associate with the  following C structure,

typedef struct{
    int  m_length;
    bool m_use_space;
}tIdent;

typedef struct
{
    // Default encoding for text
    string              m_encoding;
    
    // Plug-ins loaded at start-up
    vector<string>      m_plugins;
        
    // Tab indent size
    tIdent              m_indent;
}tSampleJson;
List 2.1

Now we have the structure but this still is useless to achieve an automatic load. We need an essential information as can be the name of associated variable in json file and the type of each variable.

The way to achieve this is declaring the type with its name through c++ classes as we can see in the following list,

typedef struct{
    CParserVarInt32<_CONST_CHAR("length")>  m_length;
    CParserVarBool<_CONST_CHAR("use_space")> m_use_space;
}tIdent;

typedef struct
{
    // Default encoding for text
    CParserVarString<_CONST_CHAR("encoding")>         m_encoding;
    
    // Plug-ins loaded at start-up
    CParserVarArrayString<_CONST_CHAR("plug-ins")>      m_plugins;
        
    // Tab indent size
    CParserVarPropertyGroup<tIdent,_CONST_CHAR("indent")> m_indent;
}tSampleJson;
List 2.2

And that's just we need. We  already have a declared C-structure type that will be associated with our json file. As we can see we can have a parse variable as type boolean, integer, array, etc. The base of the type is CParserVarPropertyGroup and represents the base of any Json structure in C and also have a basic information to iterate through all elements in the C-structure (we will see how it works at section 4).

Quote:

You may noticed that of _CONST_CHAR(s). This is a trick to pass literal string through variadic templates char by char, due that templates doesn't accept  pass literal strings (i.e const char *) as a parameter. I'm not going to explain how it works _CONST_CHAR in this article but if people want to know it, I will publish it Smile | :) . Not all compilers support this method of passing of _CONST_CHAR. For example VC++ doesn't support it.

3. Parse and load

From the data declared on List 2.2 we had seen, finally we present the code to parse and load the data,

int main(int argc, char *argv[]){

    // declare our data var interface.
    CParserVarPropertyGroup<tSampleJson> * data_json;

    // create json-parser
    CParserJson<tSampleJson> * parser = new CParserJson<tSampleJson>();

    // parse our file
    if(parser->parseFile("sample.json")){
        // get data from parser.
        data_json = parser->getData();
  
        // Then you already have all data filled in the struct data_json :)
        

    }
    
    // deallocates parser
    delete parser;

    // ... and that's all :)
    return 0;

}

List 3.1

Easy peasy. Isn't it ? Smile | :) . As you can see we need only four lines of code to get all Json variables loaded in your C++ program and then all is already ready to go!

We present also a full example to show how easily can be the operations with the variables, through overloaded C++ operators once the data is already loaded,

int main(int argc, char *argv[]){

    // declare our data var interface.
    CParserVarPropertyGroup<tSampleJson> * data_json;

    // create json-parser
    CParserJson<tSampleJson> * parser = new CParserJson<tSampleJson>();

    // parse our file
    if(parser->parseFile("sample.json")){
        // get data from parser.
        data_json = parser->getData();

         // Show the values before modifications at screen.
        std::cout << " Before modifications:"<< std::endl;
        std::cout << data_json->cpp2json();       

        // From here we can operate with loaded data in our program using c++ operators
        // put m_use_space to false...
        data_json->m_indent.m_use_space = false;

        // iterate of all m_plugins var and replace with random strings...
        for(unsigned i = 0; i < data_json->m_plugins.size(); i++) {
            data_json->m_plugins[i] = "my_randomstring"+intToString(i+1);
        }
        std::cout << "---------------------------------------------------" << std::endl;
        std::cout << " After modifications:"<< std::endl;
               
        // show the modifications at screen (it can be saved in file too)
        std::cout << data_json->cpp2json();

    }
    
    // deallocates parser
    delete parser;

    // that's all :)
    return 0;

}
List 3.2

And after compile and its execution we would expect this result as we can see in the List 3.2 (in bold the changes)

 Before modifications:
{
    "encoding":"UTF-8",
    "plug-ins":
    [
        "python" ,"c++" ,"ruby" 
    ],
    "indent":
    {
        "length":3,
        "use_space":true
    }
}
------------------------------------------------------------------------------
 After modifications:
{
    "encoding":"UTF-8",
    "plug-ins":
    [
        "my_randomstring1" ,"my_randomstring2" ,"my_randomstring3" 
    ],
    "indent":
    {
        "length":3,
        "use_space":false
    }
}
List 3.3

 Of course, if any variable has not been parsed, due not exist or it doesn't matches its variable name json and C-structure, you have to use the isParsed() to check if the variable was parsed correctly.

For example,

if(data_json->m_encoding.isParsed()){

// value parsed ok. do something... 

}
List 3.4

Any no parsed variable, by default, the strings will be empty and the numbers and booleans will be 0  and false respectively.

4. How it works ?

Well at this point I would tell how it works the core of this small loader and how it gets all data automatically. The secret to achieve this is because memory is linear and then you can take advantage of it and iterate all data like a vector. The main loop of the parse and load data is made on the class type called CParseVarPropertyGroup. Inside it have all information to iterate through all variables. To achieve this we need two basic things,

  • Know the size of the variable
  • Know the range iteration of the main structure

4.1 Knowing the size and type of every variable

Let's see some example of the implementing classes with the initializing of its size and type called size_data and _m_iType respectively from CParserVar (the top base class),

Quote:

In the following examples I erased the using of template to initialize its variable name to make more simple the code.


//#include "CVariable.h"
enum{
    UNKNOWN_TYPE = 0, // 0
    BOOL_TYPE, // 1
    INT32_TYPE, // 2
    FLOAT_TYPE, // 4
    STRING_TYPE, // 5

    // More types to declare ...
};



class CParserVar{

public:
    int size_data;
    int _m_iType;

     CParserVar(){

          size_data = sizeof(CParserVar);
          _m_iType = UNKNOWN_TYPE; // invalid type (it will be ignored on the main loop)


     }
};


class CParserVarNamed:public CParserVar{
public:
    CParserVarNamed(){
       size_data = sizeof(CParserVarNamed);
       _m_iType = UNKNOW_TYPE; // invalid type (it will be ignored on the main loop)
    }

};

class CParserVarInt32: public CParserVarNamed{

    Sint32 int32_value; // the main value

public:

    CParserVarInt32(){
         size_data = sizeof(CParserVarInt32);
         _m_iType = INT32_TYPE; // explain that is type int32
   }
}

// More CParserXXXX types to declare...


 

List 4.1

We already know the size of every class stored in size_data.

4.2 Knowing the range iteration of the main structure

We have the size of every variable type but we still need the information of the ranges of the main structure. The main structure is the one had been instantiated with the CParseVarPropertyGroup class. For example, on the List 3.1 the tSampleJson was instantiated as the main structure.

To achieve this, let add two more variables, one of them called js_ini and declared at the CParserVarNamed,

class CParserVarNamed{

// ...

//-------------------------------
    CParseVar js_ini; // mark ini struct
    // don't put any var from HERE (only functions)!!!
    //    |
    //    |
    //   \/
//-------------------------------



};
List 4.2

 

Quote:

As you can see in the code any variable must be decalred from js_ini start.

And js_end declared in the CParseVarPropertyGroup

template<typename _T>
class CParseVarPropertyGroup:public CParserVarNamed, public _T{


     //---------------------------------------
     //     |
     //     |
     //    \/
     // Don't put any var until here (only functions)!!!
     //
     CParser   js_end; // mark end struct
     //---------------------------------------

     //...

};
List 4.3
Quote:

As you can see in the code any variable must be decalred until js_end.

The lists 4.2 and 4.3 explain the main trick. CParserVarNamed have only a variable called js_ini and the other one called js_end is declared after the data instantiated _T (in this case whole tSampleJson structure) of the CParserVarPropertyGroup. We show the following image to ilustrate how the memory map is done,

Image 4.1

As we can see on the image 4.1 you can iterate across the main structure js_ini until reach js_end pointer. Exactly, the main structure starts on ((CParseVar *)&js_ini+1) and ends at ((CParseVar *)&js_end-1). Now, we now can understant why we can't declare any variable between js_ini and js_end, else it won't work. We have all information to iterate through the main struct. Let's see the main loop of the loader,

    CParseVarPropertyGroup<tSampleJson>   *c_data;

    char *aux_p = (char *)((CParserVar *)&c_data->js_ini + 1);
    char *end_p = (char *)((CParserVar *)&c_data->js_end - 1);

    //iterating through struct C++
    for(;aux_p < end_p; ) {

        CParserVar * p_sv = (CParserVar *)aux_p;

        switch(p_sv->_m_iType){
        default:
        case UNKNOWN_TYPE:
                break;
        case INT32_TYPE:

                 ....

                 break;

        case STRING_TYPE:

                ....

                break;

        }    

        // iterate to next var...
        aux_p += p_sv->size_data; 
    }
List 4.4

And with the List 4.4 we have shown how to get all data automatically. Until here I've told how the main loop  works.

5. Compile and use the code

I've attached the FastJson2Cpp.tar.gz file that includes all code of this Json parser and loader. Inside, you will find the source code plus the main sample app with the json file that you already have seen on this article. The main files of this parser are CParserJson.h, CParserJson.cxx and CParserVar.h. The other ones are utilities to print, access to a file and so on. 

The code only works with a gnu compiler and also it needs re2 library. The direct way to compile all code is doing the following at the console,

g++ *.cpp -std=c++0x -o sample

And then run the example that loads sample.json,

./sample

I hope the article be for your interest. Sorry for my broken English, I'll improve in the end I promise you Smile | :)

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share

About the Author

jespa007
Software Developer (Senior)
Spain Spain
I'm a happy person who likes programming. It has been a passion since my nineteen's Smile | :) Smile | :) .

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140921.1 | Last Updated 12 Aug 2014
Article Copyright 2014 by jespa007
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid