2

I would like to know how to read/write a JSON file using C++. I will be using this file to store player info & setting for a simple game I'm making. It's nothing fancy, just a console number guessing game, but I just use it to learn stuff.

I have to know how to read & write specific parts of a JSON.

Jean-Baptiste Yunès
  • 34,548
  • 4
  • 48
  • 69
Cubicalspy
  • 31
  • 1
  • 5
  • C++ does not have a builtin library for that. To properly read a JSON file, you might look for other libraries to do it. However, if your file is simple enough, you might just want to parse it yourself. – Ranoiaetep Jan 12 '22 at 16:05
  • 2
    Yes use a library, no need to invent and test your own code. If you use visual studio there is an nuget package for nlohmann.json that you can easily install. Introduction here : https://kezunlin.me/post/f3c3eb8/, git repository here : https://github.com/nlohmann/json – Pepijn Kramer Jan 12 '22 at 16:06
  • Don't change the title with tags like _[solved]_ or such. Mark the answer which solved your problem best as accepted (big checkmark at the left) instead. – πάντα ῥεῖ Jan 12 '22 at 16:29
  • For very simple JSON output you can use string streams and "R" strings. For anything else, I would be inclined to use jsoncpp - https://github.com/open-source-parsers/jsoncpp – Den-Jason Jan 12 '22 at 16:35
  • If you are allergic to libraries, you should download the JSON file format specification, which will tell you how to parse a JSON file. – Thomas Matthews Jan 12 '22 at 17:32
  • If you are looking to serialize and deserialize JSON files with a static structure, please have a look at https://github.com/Chlumsky/json-cpp-gen – Detheroc May 04 '22 at 13:08

3 Answers3

7

Using a library, it can be done quite easily:

#include <nlohmann/json.hpp>
#include <iostream>

int main() {
    // read file
    auto json = nlohmann::json::parse("{\"value1\": \"string\"}");

    // mutate the json
    json["value1"] = "new string";

    // write to a stream, or the same file
    std::cout << json; // print the json
}

C++ don't have the built-ins for dealing with json. You can implement your own json data structure, or use one available like nlohmann/json or simdjson

You could create your own parser using pure C++ with the standard library only, but I would advise against, unless it's for learning purpose.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
0

Using struct_mapping it can be done:

#include "struct_mapping/struct_mapping.h"

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

struct Planet
{
    std::string name;
    double mass;
    bool populated;
};

int main()
{
    struct_mapping::reg(&Planet::name, "name");
    struct_mapping::reg(&Planet::mass, "mass");
    struct_mapping::reg(&Planet::populated, "populated");

    Planet planet;

    auto stream = std::ifstream("planet.json");
    struct_mapping::map_json_to_struct(planet, stream);

    planet.name = "Mars";
    planet.populated = false;

    std::ostringstream out_json_data;
    struct_mapping::map_struct_to_json(planet, out_json_data, "  ");

    std::cout << out_json_data.str() << std::endl;
}

Data file example

{
  "name": "Earth",
  "mass": 1234,
  "populated": true
}
tilin
  • 332
  • 1
  • 7
0

I wrapped boost property tree initialized around classes and macros and it's close to type reflection(but it's still missing a reflection library to finish it off). It Also supports nesting of types something that alot of so called "fantastic json" libraries fall short of when you get into the nitty gritty.

So say you have a class what you want to serialize or deserialize in JSON:

I'd write in my cpp

class MyClass: public virtual Algorithm::Interface::ISimpleSerializedType
{
    public:
    int a;
    string b;
    // could be simplified further via a variadic macro to generate //SimplePropertyTree
    virtual Algorithm::Interface::IPropertyTree SimplePropertyTree(Algorithm::Interface::IPropertyTree& pt, bool toPropertyTree)
    {
        PSER(a, int)
        PSER(b, string)
    }
};

The JSON would look something like { a : "1" b :"somestring" }

My read and write unit tests/snippets would look like this:

//write 
MyClass entity;
    entity.a = 1;
    entity.filename = "test.json";
    entity.ToFile();
    // read
    MyClass entity;
    entity.filename = "test.json";
    entity.FromFile(); // everything is loaded

code for Algorithm::Interface::ISimpleSerializedType

#ifndef I_SIMPLE_SERIALIZED_TYPE_H
#define I_SIMPLE_SERIALIZED_TYPE_H
#include "IType.h"
#include "IFileSerializer.h"
namespace Algorithm
{
    namespace Interface
    {
        // Class contract that exposes common methods for which to extend
        class ISimpleSerializedType : public virtual IType,public virtual IFileSerializer
        {
            public:
            virtual IPropertyTree  ToPropertyTree(void){
                IPropertyTree pt;
                return SimplePropertyTree(pt,true);
            };

            // method which extracts the values from property tree
            virtual void FromPropertyTree(IPropertyTree&  pt){
                auto tree = SimplePropertyTree(pt,false);
                pt = tree._pt;
            };
            
            protected:
            // need to implement this
            virtual IPropertyTree SimplePropertyTree(IPropertyTree&  pt,bool ToPropertyTree)
            {
                
                return pt;
            }
        };
    }
}
#endif

Code For ITYPE

#ifndef ITYPE_H
#define ITYPE_H
#include <sstream>
#include <string>
#include <vector>
#include <string>
#include "IPropertyTree.h"
#include <fstream>
// macross to simplify streaming property tree
#define __str__(s) #s
#define PADD(s) {\
try\
{\
std::string ss = std::to_string(s);\
std::string key = std::string(__str__(s));\
pt.add(key,ss);\
}\
catch (std::exception ex)\
{\
}\
}
#define PADDS(s) {\
try\
{\
std::string key = std::string(__str__(s));\
pt.add(key,s);\
}\
catch (std::exception ex)\
{\
}\
}
#define PADDBASE(BASE){\
auto st = std::string(__str__(BASE));\
auto pt2 = BASE##ToPropertyTree();\
pt.addPropertyTree(st, pt2);\
}
#define PADDMEMBER(membervar) {\
auto st = std::string(__str__(membervar));\
LOGIT1(st)\
auto _pt = membervar.ToPropertyTree();\
pt.addPropertyTree(st, _pt);\
}

// PGET 
#define PGET(VAR,type) {  std::string s(__str__(VAR));\
VAR = pt.get<type>(s); }

#define PGETBASE(VAR) {\
try\
{\
auto st = std::string(__str__(VAR));\
auto ptBase##VAR = pt.getChild(st); \
VAR##FromPropertyTree(ptBase##VAR);\
}\
catch (...)\
{\
}\
}
#define PGETMEMBER(membervar) {\
auto st = std::string(__str__(membervar));\
auto pt2 = pt.getChild(st);\
membervar.FromPropertyTree(pt2);\
}
///////////////
/// PGET2

#define PGET2(VAR,type) {  std::string s(__str__(VAR));\
VAR = pt._pt.get<type>(s); }

#define PGET2BASE(VAR) {\
try\
{\
auto st = std::string(__str__(VAR));\
auto ptBase##VAR = pt._pt.getChild(st); \
VAR##FromPropertyTree(ptBase##VAR);\
}\
catch (...)\
{\
}\
}
#define PGET2MEMBER(membervar) {\
auto st = std::string(__str__(membervar));\
auto pt2 = pt_pt.getChild(st);\
membervar.FromPropertyTree(pt2);\
}





// PSerialize uses a implied type bool ToPropertyTree and pt
#define PSER(VAR,type) if(toPropertyTree) {\
std::cout << "padd" << std::endl;\
PADD(VAR)\
} else {\
std::cout << "pget" << std::endl;\
PGET(VAR,type)\
}
#define PSERS(VAR) if(toPropertyTree) {\
PADDS(VAR)\
} else {\
PGET(VAR,std::string)\
}
#define PSERBASE(VAR)if(toPropertyTree) {\
PADDBASE(VAR)\
} else {\
PGET2BASE(VAR)\
}

#define PSERMEMBER(membervar)if(toPropertyTree) {\
PADDMEMBER(membervar) \
} else {\
PGET2MEMBER(membervar) \
}

namespace Algorithm
{
    namespace Interface
    {
        // Class contract that exposes common methods for which to extend
        class IType
        {
            public:
                IType() {};
            // causes problems with hiberlite when you derive it
            // from MVC so omitting this
            //  IType(IType& rhs) { *this = rhs; }
            virtual ~IType(){}; // destructor
            // methods don't communicate tho the key just the value

            // like stl containers returns size of type
            virtual size_t size(void){ return sizeof(IType);};

            // says the maximum size of the type
            virtual size_t max_size(void) { return sizeof(IType); };

            virtual void ToString(char* data,size_t& dataSize){ /* not implemented*/ };
            virtual void FromString(char* data,size_t& dataSize){};

            IType& operator=(const IType& rhs){
                std::string s;
                IType& rhsRef = const_cast<IType&>(rhs);
                size_t size = rhsRef.size();
                s.resize(size);
                rhsRef.ToString(const_cast<char*>(s.c_str()), size);
                FromString(const_cast<char*>(s.c_str()),size);
                return *this;
            };


            // must be friended methods
            // istream extraction operators terminated by std::endl for each respective subtype
            // ostream extraction operators terminated by std::endl for each respective subtype

            // encode the stream to stream with variable name + value name. Useful for key value streams;
            virtual IPropertyTree  ToPropertyTree(void){
                IPropertyTree pt;
                return pt;
            };

            // method which extracts the values from property tree
            virtual void FromPropertyTree(boost::property_tree::ptree&  typesEncodedInAPropertyTree){
                IPropertyTree pt;
                pt._pt = typesEncodedInAPropertyTree;
                FromPropertyTree(pt);
            };

            // method which extracts the values from property tree
            virtual void FromPropertyTree(IPropertyTree& typesEncodedInAPropertyTree) {

            };

            // call a serializer here
            // method instructs how to write to file by calling the approppriate serializer
            virtual void ToFile(void){

            };

            virtual void FromFile(void) {};
            virtual std::string TypeName(void) { return ""; };

        protected:
            inline bool exist(const std::string& name)
            {
                std::ifstream file(name);
                if (!file)            // If the file was not found, then file is 0, i.e. !file=1 or true.
                    return false;    // The file was not found.
                else                 // If the file was found, then file is non-0.
                    return true;     // The file was found.
            }
        };
    }
}
#endif

Code For IPropertyTree

#ifndef I_PROPERTY_TREE_H
#define I_PROPERTY_TREE_H
#include <boost/property_tree/ptree.hpp>
#include <memory>
#include <map>
#include <string>
#include <vector>
#include <iostream>
namespace Algorithm
{
    namespace Interface
    {
        class IPropertyTree
        {
            const std::string attributePrefix = ".<xmlattr>."; // attribute prefix to reference a attribute within boost property tree
            // https://stackoverflow.com/questions/3690436/how-are-attributes-parsed-in-boost-propertytree
            std::string BuildAttributeInsertionKey(std::string& key, std::string& attributeKey) { return key + attributePrefix + attributeKey; };

            public:
                boost::property_tree::ptree _pt; // good reference reading https://theboostcpplibraries.com/boost.propertytree
                    const IPropertyTree& operator=(const IPropertyTree& pt){
                    this->_pt = pt._pt;
                    return *this;};
                IPropertyTree(void) :_pt() {};
                IPropertyTree(boost::property_tree::ptree& pt) : _pt(pt) {};
                // usually only accessed by the serializers don't manually edit this
                boost::property_tree::ptree& GetBoostPropertyTree(void) { return _pt; };
                #ifdef _WIN32
                // key/value get and set
                template <class T>
                    void add(std::string& key, T& value)
                    {
                    _pt.put(key, value);
                };
                #else
                template <class T>
                    void add(std::string key, T value)
                    {
                    _pt.put(key, value);
                };
                #endif
                template <class T>
                T get(std::string& path) {
                    return  _pt.get<T>(path);
                };
                // attribute get/set
                template <class T>
                void addAttribute(std::string& keyName, std::string& attributeKey, T& attributeValue) {
                    _pt.add(BuildAttributeInsertionKey(keyName, attributeKey), std::to_string(attributeValue));
                }

                IPropertyTree getChild(std::string& key)
                {
                    return IPropertyTree(_pt.get_child(key));
                }

                template <class T>
                T getAttribute(std::string& keyPath, std::string& attributeName) {
                    return  _pt.get<T>(BuildAttributeInsertionKey(keyPath, attributeName));
                }


            void addPropertyTree(std::string& keyOfChildTree,IPropertyTree& tree)
                {
                _pt.add_child(keyOfChildTree,tree.GetBoostPropertyTree());
            };

            void addAttribute(std::string& keyName,std::string& attributeKey, std::string& attributeValue)
            {
                    _pt.add(BuildAttributeInsertionKey(keyName,attributeKey), attributeValue);
            };
        };
    }
}
#endif

Code For IFileSerializer
#ifndef I_FILE_SERIALIZER_H
#define I_FILE_SERIALIZER_H
#include "IJSONSerialize.h"
#include "IType.h"
#include "../../Tools/Diagnostics/Logger/Logger.h" // this uses LOGIT but you can just replace with std::cout
#include <cstdint>
#include <cstdlib>
#include <string>

namespace Algorithm
{
    namespace Interface
    {
        class IFileSerializer;
        // a Serializer for JSON
        class IFileSerializer : public virtual Algorithm::Interface::IType
        {
           public:

           std::string filename;
           IFileSerializer(void):Algorithm::Interface::IType(),filename(){};

          virtual void ToFile(void)
         {
                std::string msg = TypeName() + "::ToFile()";
                LOGIT1(msg)
                    std::string testJSON(filename);
                    auto pt = ToPropertyTree();
                    msg = TypeName() + "::ToFile() calling IJSON serialize";
                LOGIT1(msg)
                    Algorithm::Interface::IJSONSerialize test(testJSON, pt);
                msg = TypeName() + "::ToFile() WriteFile";
                LOGIT1(msg)
                    test.WriteFile();
        };


        virtual void FromFile(void)
        {
            auto msg = TypeName() + "::FromFile()\n";
            LOGIT1(msg)
            std::string testJSON(filename);
            auto pt = ToPropertyTree();
            Algorithm::Interface::IJSONSerialize test(testJSON, pt);
            test.ReadFile();
            this->FromPropertyTree(test.GetPropertyTree());
        };

        virtual Algorithm::Interface::IPropertyTree  ToPropertyTree(void) { Algorithm::Interface::IPropertyTree pt; return pt;};

        // method which extracts the values from property tree
        virtual void FromPropertyTree(Algorithm::Interface::IPropertyTree& pt) {};

        void ParseServerArgs(char** argv, int argc){
            std::string msg2="IFileSerializer::ParseServerArgs";
            LOGIT1(msg2)
            filename = "config.json";
            if(exist(filename))
            {
                std::string msg = "IFileSerializer::Calling FromFile";
                LOGIT1(msg)
                FromFile();
            }
            else
            {
                std::string msg = "IFileSerializer::Calling ToFile";
                LOGIT1(msg)
                ToFile(); // write it back so next time you can feed in the json
            }
        };
        }; // end class
    }
}
#endif

IJSONSerialize Code

#ifndef IJSONSERIALIZE_H
#define IJSONSERIALIZE_H
#include <string>
#include <vector>
#include <iostream>
#include <boost/property_tree/json_parser.hpp>
#include "IPropertyTree.h"
namespace Algorithm
{
    namespace Interface
    {
    // object that provides facilities to serialize JavaScript Object Notation(JSON)
    // citation: https://stackoverflow.com/questions/4586768/how-to-iterate-a-boost-property-tree
    class IJSONSerialize
    {
        IPropertyTree _pt;
        std::string _filename;
        public:
        IJSONSerialize(const std::string& filename, IPropertyTree& pt):_pt(pt),_filename(filename){

        };
    
    virtual void WriteFile(void){
        try
        {
            boost::property_tree::json_parser::write_json(_filename, _pt.GetBoostPropertyTree());
        }
        catch(std::exception ex)
        {
            std::cerr << "can't write json file " << _filename;
        }
    };

    virtual void WriteAsAString(std::string& outString)
    {
        std::stringstream ss;
        boost::property_tree::write_json(ss, _pt.GetBoostPropertyTree());
        outString = ss.str();
    };
    
    virtual void ReadFile(void){
         try 
         {
             boost::property_tree::read_json(_filename, _pt.GetBoostPropertyTree());
         }
         catch(const boost::property_tree::json_parser_error &jpe)
         {
                //do error handling
                std::cerr << "can't read json file " << _filename <<jpe.what();
         }
    };
    
    virtual void ReadFromString(std::string& s){
        try
        {
         std::stringstream ss;
         ss << s;
        auto pt = _pt.GetBoostPropertyTree(); boost::property_tree::json_parser::read_json(ss, pt);
        }
        catch(std::exception)
        {
            
        }
    };
    virtual std::string WriteToString(void){
        std::stringstream ss;
        boost::property_tree::json_parser::write_json(ss,_pt.GetBoostPropertyTree());
        return ss.str();
    };
    
    // use to retrieve all the values but
    virtual IPropertyTree& GetPropertyTree(void){
        return _pt;
    };
    
};
    }
}
#endif

If any code missing you can find it in my bitbucket crossplatform C++ network template that's built on top of boost asio. The code is here: https://bitbucket.org/ptroen/crossplatformnetwork/src/master/

And again if you missed the comment and don't want to use LOGIT you can just find and replace with std::cout

Note code above is working but if you study enough their is some tech debt that could be optimized even more like reflection

Anyways hope you find this useful

ptroen
  • 21
  • 3