Rapidjson vs JsonCPP

Hi,

Is anyone else having trouble with rapidjson library? It’s is so hard to use, and unintuitive with very bad documentation compared to JsonCPP. I take it that cocos switched to rapidjson because it’s fast? I wish it somehow reverted to JsonCpp…

For example, I need to get a vector of primitives from a Json document. The documentation says:

for (Value::ConstValueIterator itr = a.Begin(); itr != a.End(); ++itr)
    printf("%d ", itr->GetInt());

That means I have to write this for every type myself… I would like to use a template and a conversion operator instead :frowning:

std::vector<T> vect;
for (auto itr = value.onBegin() ; itr != value.onEnd() ; ++itr)
{
    vect.push_back(T(*itr));
}

I’m using libjson and completely happy with it. Well, almost: sometimes it can’t parse comments correctly, but since comments are not part of JSON-standard, it’s ok.

1 Like

This actually looks pretty decent:

At least it is maintained, and we can submit PRs with improvements and such. Would it be feasible to move to git submodules with this one?

After looking into this more, it is probably impossible for the cocos2dx guys to pick the ideal JSON library. Each library has its ups and downs… Better just choose your own and go forward.

I forked the JsonCPP library, added a few things, and now it is working wonderfully.

The doc reference I used:
http://jsoncpp.sourceforge.net/old.html

My fork:
https://github.com/Mazyod/jsoncpp

Sample code:

User User::loadUser()
{
    const auto& string = UserDefault::getInstance()->getStringForKey("KDAPI::User::savedAccount");
    
    if (string.length() > 0)
    {
        Json::Value root;
        Json::Reader reader;
        
        if (reader.parse(string, root))
        {
            return User(root);
        }
        
        CCASSERT(false, "Couldn't parse user JSON");
    }
    
    return User::createGuestUser();
}

User::User(Json::Value& value)
: Player(value)
, leaderboardRank(value["leaderboard_rank"])
, stats(value["stats"])
{
}

void User::save() const
{
    Json::Value root;
    save(root);
    
    Json::StyledWriter writer;
    UserDefault::getInstance()->setStringForKey("KDAPI::User::savedAccount", writer.write(root));
    UserDefault::getInstance()->flush();
}

Json::Value& User::save(Json::Value &value) const
{
    Player::save(value);
    
    Json::Value statsValue;
    
    value["leaderboard_rank"] = leaderboardRank;
    value["stats"] = stats.save(statsValue);
    
    return value;
}

I know, it is super clean compared to rapidJson :grin:

It probably makes sense to use the JSON directly with your code as you are, but what if there were a simple method to get a ValueVector/ValueMap from a JSON file in the same way that you can get one for PLIST files?

From your code Json::Value may be slightly more forgiving, but otherwise looks similar to cocos2d::Value and the latter can always be updated to improve support for data in and out of a game.

Our current workflow that is like this JSON --> script to convert to PLIST --> Read into Value[Vector|Map]

I’m looking into going straight from JSON --> Read into Value[Vector|Map] instead in our next iteration.

1 Like

Not bad at all, I haven’t thought of that. It’s a great idea. In my case, I might as well switch the backend implementation to plist (No shipped versions of the game yet). The overhead isn’t that big for me, and it’s a simple matter of changing json.dumps to plistlib.writePlistToString.

Here’s what I came up with for an initial JSON -> cocos2d::Value using rapidjson (included as part of cocostudio lib). It’s a first pass so there are possibly unnecessary copies or even memory leak, or bug in the conversion. But it’s working with a couple simple .json and .plist comparison tests. Since I’ve used ValueMap and ValueVector extensively within one project since it was a migration from 2.x to 3.x and initially used plists this works for that project. I’m not sure it’ll be useful for a brand new project which could use rapidjson directly instead of cocos2d::Value as well as using standard data c++11 data types and containers. Anyway, here’s my current util class.

.h

#pragma once

#include <base/CCValue.h>
#include <json/document.h>

class STValueMapFromJSON
{
public:
    static cocos2d::Value parseValueFromJsonValue(const rapidjson::Value& jvalue);
    static cocos2d::ValueMap getValueMapFromJsonFile(const std::string& filename);
    static cocos2d::ValueVector getValueVectorFromJsonFile(const std::string& filename);
};

.cpp

#include "STValueMapFromJSON.h"
#include <json/rapidjson.h>
#include <cocos2d.h>
#include <cocostudio/DictionaryHelper.h>

//USING_NS_CC;
//using namespace cocostudio;

// TODO: add any necessary 'const', 'const' & references, or error checking
cocos2d::Value STValueMapFromJSON::parseValueFromJsonValue(const rapidjson::Value& jvalue)
{
    // parse by type
    auto t = jvalue.GetType();

    if(t == rapidjson::Type::kNullType)
    {
        // don't add anything
        return cocos2d::Value();
    }

    if(t == rapidjson::Type::kFalseType) {
        return cocos2d::Value(false);
    }

    if(t == rapidjson::Type::kTrueType) {
        return cocos2d::Value(true);
    }

    if(t == rapidjson::Type::kStringType) {
        return cocos2d::Value(jvalue.GetString());
    }

    if(t == rapidjson::Type::kNumberType) {
        if(jvalue.IsDouble()) {
            return cocos2d::Value(jvalue.GetDouble());
        } else if(jvalue.IsUint()) {
            int temp = int(jvalue.GetUint());
            return cocos2d::Value(temp);
        } else if(jvalue.IsInt()) {
            return cocos2d::Value(jvalue.GetInt());
        }
    }

    if(t == rapidjson::Type::kObjectType) {
        cocos2d::ValueMap dict;
        for (rapidjson::Value::ConstMemberIterator itr = jvalue.MemberonBegin(); itr != jvalue.MemberonEnd(); ++itr)
        {
            auto jsonKey = itr->name.GetString();
            auto el = parseValueFromJsonValue(itr->value);
            dict[jsonKey] = el;
        }
        return cocos2d::Value(dict);
    }
    if(t == rapidjson::Type::kArrayType) {
        cocos2d::ValueVector arr;
        // rapidjson uses SizeType instead of size_t.
        for (rapidjson::SizeType i = 0; i < jvalue.Size(); i++) {
            auto el = parseValueFromJsonValue(jvalue[i]);
            arr.push_back(el);
        }
        return cocos2d::Value(arr);
    }

    // none
    return cocos2d::Value();
}

cocos2d::ValueMap STValueMapFromJSON::getValueMapFromJsonFile(const std::string& filename)
{
    auto content = cocos2d::FileUtils::getInstance()->getStringFromFile(filename);

    rapidjson::Document doc;
    doc.Parse<0>(content.c_str());
    if (! doc.HasParseError())
    {
        // check that root is object not array
        auto val = parseValueFromJsonValue(doc);
        if(val.getType() == cocos2d::Value::Type::MAP) {
            return val.asValueMap();
        }
        CCLOGERROR("JSON wasn't a ValueMap/Dict");
    }

    CCLOG("JSON Parse Error: %s\n", doc.GetParseError());

    cocos2d::ValueMap ret;
    return ret;
}

cocos2d::ValueVector STValueMapFromJSON::getValueVectorFromJsonFile(const std::string& filename)
{
    auto content = cocos2d::FileUtils::getInstance()->getStringFromFile(filename);

    rapidjson::Document doc;
    doc.Parse<0>(content.c_str());
    if (! doc.HasParseError())
    {
        // check that root is object not array
        auto val = parseValueFromJsonValue(doc);
        if(val.getType() == cocos2d::Value::Type::VECTOR) {
            return val.asValueVector();
        }
        CCLOGERROR("JSON wasn't a ValueVector/Array");
    }

    CCLOG("JSON Parse Error: %s\n", doc.GetParseError());

    cocos2d::ValueVector ret;
    return ret;
}
2 Likes

As far as I know rapidjson enforces the move semantics and disable copy constructors.

//! Copy constructor is not permitted.
private:
    GenericValue(const GenericValue& rhs) {};

So I dont think returning a Value would work… except in your case where you instantiate it just before returning, your compiler might call the move constructor instead of the copy constructor. But that value cannot be copied after that.

That’s the main problem for me using rapidjson. When parsing the json data at multiple levels in my code, I have to :

  • get a string
  • do all the parsing in one place with rapidjson.
  • dump the sub part that I want into a string and return it.
  • the next function in the chain does the same.

If I don’t design it this way, and instead pass pointers everywhere, as rapidjson would want me to, there is always the risk that some other code around executes and wipe my data under my feet. Yes, I have multiple threads and update loops triggering events in different portions of the code, and I’d rather not have pointers passed around without knowing exactly the lifetime of the instance they point to.

rant
So I personally find rapidjson very uneasy to work with in a decent size project… and I can’t think of a sensible reason for a C++ library to just throw C++ memory management concepts out the window, and enforce their own.
/rant

So I just wanted to vote for the “lets change library if we find a more suitable one” party.

Definitely don’t disagree with you, but I thought I’d note that I’m capturing the native data from rapidjson containers (false, true, int, double, string) and returning a copy embedded in a cocos2d::Value. So I don’t believe the move only semantics affects what I’m doing. Our use case is loading a file from package/bundle or through HTTP mostly at launch, so one-time loading and parsing.

If JSONCpp is much better, I’ll cast my vote alongside yours in the “change to best library” party :]

Oh sorry my apologies.
I didn’t realize your Value were cocos2d::Value because of USING_NS_CC;
It makes more sense to me now, returning cocos2d::Value converted from rapidjson::Value will work.

About my usecase, I use rapidjson (just because it s already with cocos2d-x) to save my player data in json. I send this save online, and when I retrieve it I need to extract it ( sometimes part of it ) from the whole json response of the REST call. As you can imagine, my player has data in different objects that change during gameplay, and I need to “serialize” all that into json. All that while the network library might send the data that the gameplay is changing…
So passing a string around is the safest way i could find :-/

Hmm, interesting. I’m curious why you need to pass around the rapidjson instance(s)? Or are you serializing them immediately into your player data model(s)? Why pass around a string when you could serialize into and pass around a struct? Just curious.

Yes i m serializing directly in each class that store a bit of data for the player.
Multiple developers work on the project ( typically by game feature ) so each one of them knows how to serialize the class he s been working on.
Having a struct and pass it around would just add an extra layer of complexity that is not really needed i think…

Yeah, that makes sense.