Warning: technical babbling following.

As some of you might know from unofficial sources – I am working on bringin Ubuntu One to the KDE workspace. After I spent one month binding with the community it is now time to start talking about the important parts of life.

Namely: “How do I properly work with a JSON object”

It is more of a secret but there is a Ubuntu One server-side REST API to query all sorts of more or less useful information. From my point of view it is not the information that is interesting though, but how to make it useful in a Qt app ;).

So, this REST API returns a JSON “object”, and of course using JSON objects (which really are just strings, or rather variants of strings and other variant datatypes) is sort of ugly and horrible and defeats the concept of object-oriented programming. What’s more is that using those variants directly exposes internals such as actual names, instead of providing stable interfaces indepdent of the backing members. Clearly we want to convert the JSON stuff to something more C++y – fortunately enough there is QJSON.

Using QJSON one can easily convert a JSON object to a QObject. Although that is not exactly the right wording, more appropriate is: one can easily fill a QObject with data from a JSON object. Keeping that in mind is very important when working with JSON and making it a QObject.

When using the builtin magic to convert a parsed JSON QVariant to a QObject it will iterate over all QProperties defined for a given object and try to write the value of a key from a JSON QVariantMap to a QProperty with the same name.

This means that you need to define QProperties of the same name as the keys in the JSON QVariantMap, so if you have:

{ “foo”, “hello”; “bar”, 100;}

then you will need a QString property named “foo” and a int property named “bar” in order to deserialize this map. Please take a look at this blog post about serialization and deserialization of JSON and QObjects. It should make the basics very easy to understand, or so I think 🙂

Now this will probably work just fine in most cases. Most cases means for every datatype supported out-of-the-box by QVariant. If you want to deserialize a nested object though, you will have a bit of a problem there. This took me quite a while to figure out and I only noticed what went wrong by looking at the appropriate Qt code (thank god for FLOSS).

When I said that this will work for every standard QVariantable datatype, I was refering to how writing to a property works. What basically happens is that you put a QVariant in and Qt will then try to call the WRITE method of the property with the QVariant as argument. But here is the fancy thing about this: before it actually calls the setter it will check if the QVariant’ed type obtained for the setter is actually the same as the one you want to write.

Let me explain this with an example:

You have the QVariantMap from above, but this time with an addtional QVariantMap inside { “foo”, “hello”; “bar”, 100; “fluff”, {“MOTU”, “Harald”};}. What will happen if you try to convert that to a QObject is: property foo will be written using QVariant(QString), property bar will be written using QVariant(int), property fluff will be written using QVariant(QVariantMap). Now consider you want fluff to be a QObject and not a map, why can this not work considering what I wrote above about how writting a property works.

Lets recap this in detail, you have a property in the QObject that looks something like this:

Q_PROPERTY(FluffObject* fluff READ fluff WRITE setFluff)

This will rather silently fail, because what actually happens here is that you try to setFluff(QVariantMap) and not setFluff(FluffObject*) … well, to be honest it does not even get there, because Qt will catch this long before and refuse to write the property. So we have a bit of a problem. One option is of course to manually deserialize fluff with something like:

QJson::QObjectHelper::qvariant2qobject(data["fluff"].toMap(), obj->fluff());

If you have a very complex object with lots of nesting this will be rather ugly though – having this logic within the actual object we try to deseralize would be so much better, wouldnt it? So how does the imaginative developer surcome this issue?

Q_PROPERTY(QVariantMap fluff READ QVariantMap() WRITE setFluff)

Fancy, eh? 😉

So what do we do. Basically we have a QVariantMap property that we use to write the FluffyObject. When we try to write the QVariant to the property fluff it will indeed use setFluff(QVariantMap) and within that function you can then do the qvariant2qobject magic of QJSON.

I highly doubt this precise example will work for serialization though, since it is probably using the reverse approach and creates the JSON QVariant from reading the propreties, which in this case would not work terribly well, but using a real READ function should sort this, you just also have to recursively apply the serialization magic of QJSON 😉

With this approach you can not use the same property name for the QVariantMap and the real object, so either have an appropriate getter for the member in question and use it directly, or if you want to use a property, then use another name for it like “fluffObj” or something.

Happy JSONing everyone 🙂

6 thoughts on “♥ QJSON ♥

  1. Technical babbling, right 😀
    But a small offtopic feature question: Will there be Akonadi/Kontact integration with CouchDB to store PIM data in the cloud?

  2. Thanks. Exactly my problem right now. Will it work with a QVariantList ?

    Using QList is impossible cause of privacy and QList doesn’t match

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s