cpp RapidJSON - разрешает конфликты ключей без потери информации
Я хочу проанализировать текстовый файл, который похож на JSON. После некоторых преобразований символов у него все еще есть некоторые объекты, которые имеют ключевые конфликты. Так что мой JSON выглядел так:
{
"key1": {
"a": "asdf",
"a": "foo",
"a": "bar",
"a": "fdas"
}
}
И я хотел решить это в этом:
{
"key1": {
"a": [
"asdf",
"foo",
"bar",
"fdas"
]
}
}
Я пытался добиться этого с помощью JsonCpp, но он не может справиться с конфликтами ключей. Поэтому я решил использовать RapidJSON, особенно потому, что он МОЖЕТ сохранить все ключевые конфликтные элементы при разборе.
Чтобы затем разрешить конфликты ключей без потери информации, я написал следующий рекурсивный код 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
не, как я думал, возвращает итератор для всех членов с тем же именем ключа, но только для позиции первого члена с этим ключом. Я думаю, что мой образ мыслей на питоне, возможно, смешался с моими ожиданиями FindMember
, Таким образом, код будет терять "c": "fdas"
член.
Я полагался на MemberIterator EraseMember(MemberIterator first, MemberIterator last)
потому что все другие методы удаления члена, упомянутые в http://rapidjson.org/md_doc_tutorial.html, похоже, имеют проблемы с удалением последнего члена в key1
дело. Но EraseMember
как это определенно неправильный выбор для key2
дело.
Так что я как бы потерялся здесь. Может ли кто-нибудь указать мне правильное направление для разрешения ключевых конфликтов без потери информации, которая может справиться как с key1
и key2
дело?
редактировать: я использую RapidJSON с https://github.com/miloyip/rapidjson/tree/v1.0.2 который находится на v1.0.2
тег.
2 ответа
Я думаю, что сложная часть состоит в том, чтобы запомнить, был ли ключ уже расширен до массива (потому что значение может быть первоначально массивом).
Итак, другой способ - сначала преобразовать все 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);
}
Я проверял это в этом коммите.
Я полностью переписал эту часть, пытаясь (снова) другой подход. Я думаю, что нашел довольно элегантное решение:
void resolveKeyConflicts(rj::Value& value) {
if (value.IsObject()) {
std::vector<std::string> resolvedConflicts;
rj::Value newValue(rj::kObjectType);
for (rj::Value::MemberIterator vMIt = value.MemberBegin();
vMIt != value.MemberEnd(); vMIt++) {
rj::Value::MemberIterator nVFMIt = newValue.FindMember(vMIt->name);
if (nVFMIt == newValue.MemberEnd()) {
rj::Value newKey(vMIt->name, this->GetAllocator());
newValue.AddMember(newKey, vMIt->value, this->GetAllocator());
} else {
std::string conflict(vMIt->name.GetString(),
vMIt->name.GetStringLength());
if (std::find(resolvedConflicts.begin(),
resolvedConflicts.end(), conflict)
== resolvedConflicts.end()) {
rj::Value newArray(rj::kArrayType);
nVFMIt->value.Swap(newArray);
nVFMIt->value.PushBack(newArray, this->GetAllocator());
nVFMIt->value.PushBack(vMIt->value, this->GetAllocator());
resolvedConflicts.push_back(conflict);
} else {
nVFMIt->value.PushBack(vMIt->value, this->GetAllocator());
}
}
}
value.SetNull().SetObject();
for (rj::Value::MemberIterator nVMIt = newValue.MemberBegin();
nVMIt != newValue.MemberEnd(); nVMIt++) {
if (nVMIt->value.IsObject() || nVMIt->value.IsArray()) {
this->resolveKeyConflicts(nVMIt->value);
}
value.AddMember(nVMIt->name, nVMIt->value, this->GetAllocator());
}
} else if (value.IsArray()) {
for (rj::Value::ValueIterator vVIt = value.Begin(); vVIt != value.End();
vVIt++) {
if (vVIt->IsObject() || vVIt->IsArray()) {
this->resolveKeyConflicts(*vVIt);
}
}
}
}
Я не уверен в этом value.SetNull().SetObject()
часть для опорожнения value
, но это работает.
Если вы думаете, что есть возможности для улучшения, просто дайте мне знать, где. Благодарю.