Click here to Skip to main content
13,800,882 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

48.8K views
109 downloads
38 bookmarked
Posted 8 Aug 2014
Licenced MIT

ZetJsonCpp

, 10 May 2018
Rate this:
Please Sign up or sign in to vote.
An easy and quick way to load JSON files/strings into C++ structures

It can also be downloaded from github.

Introduction

Nowadays, Json structure has become the most used in web apps to interchange data. This is because Json is the simplest and most human readable format, so it is more friendly to use. In 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 easily import a json data from files or strings in C++ structures.

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 it 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 successfully 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 in the code of 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 increases, 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 employees, you will spend a lot of time on productivity !!!

But keep calm. I've developed a quick way to declare the interface with C++ and json code. My method consists of 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 is similar to 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 what 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 has 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 :). 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.

using namespace zetjsoncpp;

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->evalFile("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 ? :). 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 operations with the variables can be, through overloaded C++ operators once the data is already loaded.

using namespace zetjsoncpp;

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->evalFile("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, as it does not exist or it doesn't match 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 the core of this small loader works 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, you 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 that 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 declared from js_ini start.

And js_end is 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 declared 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 illustrate how the memory map is done.

Image 4.1

As we can see in image 4.1, you can iterate across the main structure js_ini until you 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 understand why we can't declare any variable between js_ini and js_end, else it won't work. We have all the 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 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

The source code includes the source code plus a test app with the json files. 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.

To compile the source code, you need cmake tool to automatically generate the needed files to compile the project in function of your configured compiler:

cmake

Options

-DLINK=[SHARED|STATIC]: optionally, it can pass to create shared/static library respectively

History

2018-05-15 ZetJsonCpp 1.3.0

  • Review version history to new format (MAJOR.MINOR.PATCH)
  • Behaviour on error/warning is done through a callback setFunctionOnError, setFunctionOnWarning. If these functions are not set, then the message is printed with fprintf (stderr)
  • Added memory leak detection (only for GNU toolchain). It needs memmanager https://github.org/jespa007/memmanager and pass -DMEMMANAGER parameter on cmake
  • Fixed compile test_json shared library on MSVC platform
  • Improve cmake message prints

2018-05-10 ZetJsonCpp 1.2.0

  • Project is built through cmake
  • MSVC Support (v141 Tools or MSVC++ 2015)
  • Added zetjsoncpp namespace
  • Renamed fastjsoncpp to zetjsoncpp
  • Added feature detect array types
  • Changed GPL3 license to MIT

2015-08-29 ZetJsonCpp 1.1.0

  • Added feature support number as scientific notation (i.e 2.1E-10)
  • Added feature on detecting property group or property group arrays
  • Fixed carry return line feeds compatible for Windows

2014-08-08 ZetJsonCpp 1.0.0

  • First release

License

This article, along with any associated source code and files, is licensed under The MIT License

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 | :) .

You may also be interested in...

Comments and Discussions

 
Question_CONST_CHAR(s) Pin
vsramalwan24-Sep-15 1:11
membervsramalwan24-Sep-15 1:11 
AnswerRe: _CONST_CHAR(s) Pin
jespa00730-Sep-15 22:23
memberjespa00730-Sep-15 22:23 
QuestionDoes not compile with clang Pin
Member 1068941416-Apr-15 2:06
memberMember 1068941416-Apr-15 2:06 
AnswerRe: Does not compile with clang Pin
jespa00724-Jun-15 12:48
memberjespa00724-Jun-15 12:48 
QuestionA couple of bugs and suggestions Pin
Christopher Hossack15-Apr-15 11:32
memberChristopher Hossack15-Apr-15 11:32 
AnswerRe: A couple of bugs and suggestions Pin
jespa00724-Jun-15 12:44
memberjespa00724-Jun-15 12:44 
AnswerRe: A couple of bugs and suggestions Pin
jespa00724-Jun-15 12:59
memberjespa00724-Jun-15 12:59 
GeneralMy vote of 5 Pin
koothkeeper5-Nov-14 6:15
professionalkoothkeeper5-Nov-14 6:15 
GeneralRe: My vote of 5 Pin
jespa0077-Jan-15 8:57
memberjespa0077-Jan-15 8:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.181215.1 | Last Updated 10 May 2018
Article Copyright 2014 by jespa007
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid