How to change QJsonObject value in a QJson hierarchy without using copies?

user2472658 picture user2472658 · Jun 11, 2013 · Viewed 16.4k times · Source

I am currently using Qt5.0 with the core QJson library to handle some data for the program I am developing.

To set the scene for this question I will provide you with some JSON data that illustrates my problem:

{
    "CLOCKS": [
        {
            "ID": "clk",
            "MAX": 2e+08,
            "MIN": 1e+07,
            "VALUE": "no_clock"
        },
        {
            "ID": "memclk",
            "MAX": 2e+08,
            "MIN": 1e+07,
            "VALUE": "memclk"
        }
    ]
}

Here we have a parent QJsonObject containing a single key 'CLOCKS'. The value for this key is a QJsonArray of QJsonObjects that contain a number of key/value pairs that contain my data.

If I wanted to retrieve the QJsonObject with id 'clk' I am currently using code like this:

// imagine m_data is my parent QJsonObject
QJsonArray clocks = m_data["CLOCKS"].toArray();
foreach (const QJsonValue & value, clocks) {
    QJsonObject obj = value.toObject();
    if (obj["ID"].toString() == "clk") {
        return obj;
    }
}

This works fine and the library has been great so far. However, I have started running into issues recently when I want to obtain a QJsonObject reference for modification instead of a copy.

So my question is, given the sample data provided how do I obtain a QJsonObject reference in order to modify the key/value pairs in the desired clock data object. The problem manifests itself, IMO due to the fact that you can obtain QJsonValueRefs, which are references to the value entries... but to actually access the data inside this (if the value is another array/object) you must convert using the toArray(), toObject() functions etc. This functions only return copies and not references creating a barrier to iterating down the object hierarchy with references.

The only way I have come up with so far to get around this is to create a copy of the entire "CLOCKS" QJsonArray, find the object I want then delete it and reinsert it with the changed data... and finally assign the entire array back to the "CLOCKS" key in the parent object. This seems cumbersome enough to me to me that I feel like I am doing something wrong and there must be a better way.

To support this here is what my code looks like so far... just to change the "VALUE" for one of the clock QJsonObjects:

  QJsonArray resets = m_data.value(TAG_RESETS).toArray();

  // get a copy of the QJsonObject
  QJsonObject obj;
  foreach (const QJsonValue & value, resets) {
    QJsonObject o = value.toObject();
    if (o.value(TAG_ID).toString() == id) {
      obj = o;
      break;
    }
  }

  // modify the object
  obj[TAG_VALUE] = "NEW VALUE";

  // erase the old instance of the object
  for (auto it = resets.begin(); it != resets.end(); ++it) {
    QJsonObject obj = (*it).toObject();
    if (obj.value(TAG_ID).toString() == id) {
      resets.erase(it);

      // assign the array copy which has the object deleted
      m_data[TAG_RESETS] = resets;
      break;
    }
  }   

  // add the modified QJsonObject
  resets.append(obj);

  // replace the original array with the array containing our modified object
  m_data[TAG_RESETS] = resets;

I know this could be shortened a little bit but it still seems like there must be a better way to change a single value in a QJson object hierarchy without going to all this effort!!!

Answer

George Y. picture George Y. · Mar 31, 2015

After wasting three hours of my life I can confirm that as of today this is still impossible with Qt 5.4. You can modify JSON objects, but not nested JSON objects.

The problem is that the code such as:

json["aa"].toObject()["bb"] = 123;

essentially means the following:

QJsonObject temp = json["aa"].toObject();
temp["bb"] = 123;

and since temp is not a reference but object (and toObject() doesn't return a reference), the assignment is compiled but then discarded.

Essentially it breaks down to the fact that it is impossible to obtain the reference to an object you just created, meaning you cannot create them from left to right, i.e. aa["bb"] -> aa["bb"]["cc"] etc - you cannot obtain reference to aa["bb"], only a copy of its value.

What IS possible though is to recreate the JSON with a new value added, as described here: https://forum.qt.io/topic/25096/modify-nested-qjsonvalue/4 - note that this keeps recreating the object each time it is called, and is essentially memory usage disaster, but this is all Qt currently allows.