如何在不使用副本的情况下更改QJson层次结构中的QJsonObject值?

22

我目前正在使用Qt5.0和核心QJson库来处理我正在开发的程序的一些数据。

为了解释我的问题,我将提供一些JSON数据:

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

这里有一个包含单个键“CLOCKS”的父级QJsonObject。该键的值是一个包含许多键/值对且包含我的数据的QJsonObjects数组。

如果我想要检索id为“clk”的QJsonObject,我目前正在使用以下代码:

// 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;
    }
}

目前这个库的工作正常,而且一直表现不错。然而,最近我遇到了一些问题,当我想要获取一个QJsonObject 引用 以进行修改时,而不是一个副本。

我的问题是,鉴于所提供的示例数据,我该如何获取QJsonObject引用,以便修改所需的时钟数据对象中的键值对。问题表现出来,我认为是因为可以获得QJsonValueRefs,它们是值条目的引用...但是要实际访问此内部数据(如果值是另一个数组/对象),则必须使用toArray()、toObject()函数等进行转换。这些函数只返回副本,而不是引用,从而创建了一个使用引用在对象层次结构中迭代的障碍。

到目前为止,我想到的唯一解决方法是创建"CLOCKS" QJsonArray的整个副本,找到我想要的对象,然后删除它并重新插入带有更改数据的对象......最后将整个数组分配回父对象中的"CLOCKS"键。这个过程对我来说似乎很繁琐,让我觉得我在做错什么,肯定有更好的方法。

为了支持这一点,这是我代码的样子,只是为了更改一个时钟QJsonObjects的“VALUE”:

  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;

我知道这段代码可能可以缩短一点,但是似乎还有更好的方法可以在不那么费力的情况下更改QJson对象层次结构中的单个值!


我找不到任何方法,这很奇怪。旧的QtScript模块支持此功能,您可以复制QScriptValue。对副本对象进行修改会导致所有其他副本都被修改。但QScriptObject不能这样做。 - Pavel Strakhov
3个回答

17

在浪费了我三个小时的生命后,我可以确认到目前为止,在Qt 5.4中仍然无法做到这一点。您可以修改JSON对象,但不能修改嵌套的JSON对象。

问题在于以下代码:

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

本质上意味着以下内容:

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

由于 temp 不是引用而是对象(且 toObject() 不返回引用),所以赋值被编译但随后被抛弃。

基本上这归结于一个事实:不可能获取到刚创建的对象的引用,也就是说你不能从左到右地创建它们,例如 aa["bb"] -> aa["bb"]["cc"] 等等,你只能获得 aa["bb"] 的副本而非其引用。

不过,可以通过在 JSON 中添加新值来重新创建 JSON,详见此处:https://forum.qt.io/topic/25096/modify-nested-qjsonvalue/4。需要注意的是,每次调用都会重新创建对象,这实际上会导致内存使用问题,但这目前是 Qt 允许的唯一方式。


1
还没有解决。您可以在此处检查toObject的当前原型:http://doc.qt.io/qt-5/qjsonvalue.html#toObject - 只要toObject返回一个值而不是引用,问题就会保留。只有当有一个返回QJsonObject&的函数时,问题才会得到解决。 - George Y.
正确的,我为自己做了一些测试,确实没有修复。非常烦人。 - RvdK
好的,我不能修改,但是至少可以读取吗?我的意思是这样的代码能够工作吗?只是用于读取而已?json["aa"].toObject()["bb"].toString(); - Xsmael
1
@Xsmael:是的,你可以像那样在Qt中读取JSON。只是不能修改。 - George Y.
1
为了澄清而言,Qt 5.6/5.7中没有做出任何更改。 - George Y.
多年过去了,人们仍然因为这个问题浪费时间 >:-{ - Sergey Kolesnik

11
根据实际编写Qt5中的QJson的开发人员提供的信息,目前在Qt中包含的是“只读”实现,以提供解析功能。他有意在未来扩展设计以支持“引用”,但目前尚未完成。

好的,我不能修改,但是,我至少可以阅读吗?我的意思是这样的代码会工作吗?仅供阅读?json ["aa"]。toObject()["bb"]。toString(); - Xsmael
1
有没有Qt问题可以跟踪这个功能请求? - Ross Rogers
1
他们并不真正将此视为问题 =) - evilruff
QTBUG-25723 似乎相关,但已经闲置了很长时间。 - Mike

0
我遇到了类似的问题几天了,但我已经找到了一个可行的解决方法并想在这里分享一下。
您可以导航到要更新其键值的对象。然后使用“remove”方法删除键值对,然后使用“insert”方法再次插入具有新值的键值对。
这可能会破坏对象中键值对的顺序,但由于您将通过键访问它们,所以不应该成为问题。
我发现不支持原地更改值,这是我通过艰难的方式得出的结论 :)

你能提供一个例子吗?你具体是如何建议“导航到对象”的呢? - Sergey Kolesnik
嗨,这是来自一个旧项目的代码,希望能对你有所帮助。我不知道如何插入换行符,所以请谅解。如果可以的话,请随意编辑这段注释内容:QJsonObject tempPrm; tempPrm = paramList[0]; //获取第一个参数 QJsonValue tempVal = tempPrm.value("value"); tempPrm.remove(QString("value")); tempPrm.insert(QString("value"), tempVal+1); - ACBlue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接