c++ - cpp RapidJSON - 在不丢失信息的情况下解决 key 冲突

标签 c++ json rapidjson

我想解析一个类似于 JSON 的文本文件。经过一些字符转换后,它仍然有一些对象,这些对象存在键冲突。所以我的 JSON 看起来像这样:

{
    "key1": {
        "a": "asdf",
        "a": "foo",
        "a": "bar",
        "a": "fdas"
    }
}

我想把它解决成这样:

{
    "key1": {
        "a": [
            "asdf",
            "foo",
            "bar",
            "fdas"
        ]
    }
}

我尝试用 JsonCpp 实现这个,但是它不能处理键冲突。所以我选择使用 RapidJSON,特别是因为它可以在解析时保留所有键冲突成员。

为了在不丢失信息的情况下解决 key 冲突,我编写了以下递归 RapidJSON cpp 代码:

void resolveKeyConflicts(rj::Value& value) {
    if (value.IsObject()) {
        std::map<std::string, unsigned int> nameCount;
        for (rj::Value::MemberIterator vMIt = value.MemberBegin();
                vMIt != value.MemberEnd(); vMIt++) {
            std::string name(vMIt->name.GetString());
            if (nameCount.find(name) == nameCount.end()) {
                nameCount[name] = 1;
            } else {
                nameCount[name] += 1;
            }
        }

        for (std::map<std::string, unsigned int>::iterator nCIt =
                nameCount.begin(); nCIt != nameCount.end(); nCIt++) {
            if (nCIt->second > 1) {
                rj::Value newArray(rj::kArrayType);
                for (rj::Value::MemberIterator vFMIt = value.FindMember(
                        nCIt->first.c_str()); vFMIt != value.MemberEnd();
                        vFMIt++) {
                    if (vFMIt->name.GetString() == nCIt->first) {
                        rj::Value value(vFMIt->value, this->GetAllocator());
                        newArray.PushBack(value, this->GetAllocator());
                    }
                }

                value.EraseMember(value.FindMember(nCIt->first.c_str()),
                        value.MemberEnd());
                rj::Value key(nCIt->first.c_str(), nCIt->first.length(),
                        this->GetAllocator());
                value.AddMember(key, newArray, this->GetAllocator());
            }
        }

        for (rj::Value::MemberIterator vMIt = value.MemberBegin();
                vMIt != value.MemberEnd(); vMIt++) {
            if (vMIt->value.IsObject() || vMIt->value.IsArray()) {
                resolveKeyConflicts(vMIt->value);
            }
        }
    } else if (value.IsArray()) {
        for (rj::Value::ValueIterator vVIt = value.Begin(); vVIt != value.End();
                vVIt++) {
            resolveKeyConflicts(*vVIt);
        }
    }
}

只要冲突的关键成员是该对象中的唯一成员,这就非常有效。我认为,这可以用更简单的代码进行归档,但我还尝试能够像这样解决任意键冲突:

{
    "key2": {
        "a": "asdf",
        "b": "foo",
        "b": "bar",
        "c": "fdas"
    }
}

进入这个:

{
    "key2": {
        "a": "asdf",
        "b": [
            "foo",
            "bar"
        ],
        "c": "fdas"
    }
}

事实证明,FindMember 并没有像我想的那样返回一个遍历所有具有相同键名的成员的迭代器,而只是返回具有该键的第一个成员的位置。我认为我的 Python 思维方式可能与我对 FindMember 的期望相悖。所以像这样,代码将丢失 "c": "fdas" 成员。

我依赖于 MemberIterator EraseMember(MemberIterator first, MemberIterator last) 因为所有其他删除成员的方法都在 http://rapidjson.org/md_doc_tutorial.html#ModifyObject 中提到删除 key1 案例中的最后一个成员似乎有问题。但是像这样的 EraseMember 绝对是 key2 情况下的错误选择。

所以我有点迷路了。请有人指出正确的方向来解决 key 冲突而不丢失信息,这可以同时处理 key1key2 情况?

编辑:我正在使用来自 https://github.com/miloyip/rapidjson/tree/v1.0.2 的 RapidJSON在 v1.0.2 标签中。

最佳答案

我觉得比较棘手的是要记住key是否已经展开成数组(因为value可能本来就是一个数组)。

因此,另一种方法是先将所有key:value转换为key:[value],进行合并,然后再转换回key:value 如果数组中只有一个元素。

这是我的尝试:

static void MergeDuplicateKey(Value& v, Value::AllocatorType& a) {
    if (v.IsObject()) {
        // Convert all key:value into key:[value]
        for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr)
            itr->value = Value(kArrayType).Move().PushBack(itr->value, a);

        // Merge arrays if key is duplicated
        for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd();) {
            Value::MemberIterator itr2 = v.FindMember(itr->name);
            if (itr != itr2) {
                itr2->value.PushBack(itr->value[0], a);
                itr = v.EraseMember(itr);
            }
            else
                ++itr;
        }

        // Convert key:[values] back to key:value if there is only one value
        for (Value::MemberIterator itr = v.MemberBegin(); itr != v.MemberEnd(); ++itr) {
            if (itr->value.Size() == 1)
                itr->value = itr->value[0];
            MergeDuplicateKey(itr->value, a); // Recursion on the value
        }
    }
    else if (v.IsArray())
        for (Value::ValueIterator itr = v.Begin(); itr != v.End(); ++itr)
            MergeDuplicateKey(*itr, a);
}

我在 this commit 中测试过它.

关于c++ - cpp RapidJSON - 在不丢失信息的情况下解决 key 冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35222230/

相关文章:

c++ - 无法在 RapidJSON 函数调用中使用 std::string 变量

c++ - GCC 编译器是否可以处理任何其他大于 long long int 的数据类型?

c++ - 如何在二叉搜索树中查找元素?

c++ - 具有多个主要功能的循环生成文件

javascript - jQuery + JSON - .each 不检索唯一值

c# - 反序列化 Json 数组和简单对象

c++ - 带有 rapidjson 的奇怪成员名称字符串

c++ - 原子线程栅栏 : Why is there a data race on this non atomic variable? 这有关系吗?

java - 将 JSON 响应存储在数组中

c++ - 如何使用整数作为键/名称将成员添加到 rapidjson 文档?