Simple JSON Parser / Serializer in Qt

Today XML and JSON are hot formats for data exchange. While Qt natively supports XML, it doesnt support JSON (well directly). Most developers use external libraries like qjson to serialize QVariantMap objects to a json-string and parse json-string into QVariantMap. One of the key disadvantages of using qjson (or maybe even other libraries) is the license. qjson for example is a LGPL library, which means using it in mobile phone apps might not be a good idea, where the general interest is to not have external dependencies. So the question is – “is it possible to have a JSON serializer/parser using Qt only – without too much of an effort. The answer is YES!

We would want to have a really simple class that does all the serializing and parsing, so we declare a class like the one below.

struct JSONData;
class JSON
{
public:
    static JSON& instance();
    ~JSON();

    QVariantMap parse(const QString& string) const;
    QString serialize(const QVariant& value) const;

protected:
    JSON();

private:
    JSONData* d;
};

We begin implementing the class as follows. The static instance() method is implemented as

JSON& JSON::instance()
{
    static JSON theInstance;
    return theInstance;
}

That was easy! We now implement the constructor. One of the tricks we will be using in the implementation is this – we make use of the JavaScript JSON object to perform the serializeing and parsing for us. Towards that, we implement the constructor as follows

struct JSONData
{
    QScriptEngine engine;
    QScriptValue parseFn;
    QScriptValue serializeFn;
};

JSON::JSON()
{
    d = new JSONData;

    const QString script = "function parse_json(string) { return JSON.parse(string); }n"
                           "function serialize_json(object) { return JSON.stringify(object); }";
    QScriptValue result = d->engine.evaluate(script);

    d->parseFn = d->engine.globalObject().property("parse_json");
    d->serializeFn = d->engine.globalObject().property("serialize_json");
}

Notice how we are creating JavaScript functions to parse and serialize objects. In the constructor we basically QScriptValues of the parse and serialize functions. Next, lets see how the parse() function is implemented.

QVariantMap JSON::parse(const QString& string) const
{
    QScriptValue result = d->parseFn.call(QScriptValue(), QScriptValueList() << QScriptValue(string));
    QVariantMap resultMap = result.toVariant().toMap();
    return resultMap;
}

Really really simple isnt it!

The serialize() function is equally simple – but for a CreateValue function dependency.

QString JSON::serialize(const QVariant& value) const
{
    QScriptValue arg = ::CreateValue(value, d->engine);
    QScriptValue result = d->serializeFn.call(QScriptValue(), QScriptValueList() << arg);
    QString resultString = result.toString();
    return resultString;
}

The CreateValue function basically converts any QVariant to a QScriptValue. QtScript module doesnt make it easy for us (no QScriptEngine::newVariant() is not useful here for all cases). We implement the CreateValue function as follows

QScriptValue CreateValue(const QVariant& value, QScriptEngine& engine)
{
    if(value.type() == QVariant::Map)
    {
        QScriptValue obj = engine.newObject();

        QVariantMap map = value.toMap();
        QVariantMap::const_iterator it = map.begin();
        QVariantMap::const_iterator end = map.end();
        while(it != end)
        {
            obj.setProperty( it.key(), ::CreateValue(it.value(), engine) );
            ++it;
        }

        return obj;
    }

    if(value.type() == QVariant::List)
    {
        QVariantList list = value.toList();
        QScriptValue array = engine.newArray(list.length());
        for(int i=0; i<list.count(); i++)
            array.setProperty(i, ::CreateValue(list.at(i),engine));

        return array;
    }

    switch(value.type())
    {
    case QVariant::String:
        return QScriptValue(value.toString());
    case QVariant::Int:
        return QScriptValue(value.toInt());
    case QVariant::UInt:
        return QScriptValue(value.toUInt());
    case QVariant::Bool:
        return QScriptValue(value.toBool());
    case QVariant::ByteArray:
        return QScriptValue(QLatin1String(value.toByteArray()));
    case QVariant::Double:
        return QScriptValue((qsreal)value.toDouble());
    default:
        break;
    }

    if(value.isNull())
        return QScriptValue(QScriptValue::NullValue);

    return engine.newVariant(value);
}

Thats it! You can now simply make use of JSON class to parse and serialize JSON objects.

// Parsing json strings
QString jsonString = ....
QVariantMap jsonObject = JSON::instance().parse(jsonString);

// Serializing json objects
QVariantMap jsonObject = ...
QString jsonString = JSON::instance().serialize(jsonObject)

Posted

in

by

Tags: