2016-02-05 72 views
0

我想分析一個類似於JSON的文本文件。經過一些字符轉換後,它仍然有一些對象,這些對象存在關鍵衝突。所以,我的JSON是這樣的:cpp RapidJSON - 解決沒有信息丟失的密鑰衝突

{ 
    "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不,正如我所想的那樣,使用相同的密鑰名稱爲所有成員提供一個迭代器,但只是使用該密鑰的第一個成員的位置。我認爲我的python思維方式可能與我對FindMember的期望相矛盾。所以像這樣,代碼將會失去"c": "fdas"成員。

我依靠MemberIterator EraseMember(MemberIterator first, MemberIterator last),因爲刪除http://rapidjson.org/md_doc_tutorial.html#ModifyObject中提到的成員的所有其他方法在刪除key1情況下的最後一個成員似乎都有問題。但EraseMember這樣的情況絕對是key2的錯誤選擇。

所以我有點迷失在這裏。可以請有人指出我正確的方向來解決沒有信息丟失的關鍵衝突,它可以處理key1key2案件?

編輯:我使用https://github.com/miloyip/rapidjson/tree/v1.0.2的RapidJSON,位於v1.0.2標記處。

+0

重複的鍵不是有效的JSON。 –

+0

@DavidHammen在ECMA-404中,「對象結構表示爲一對圍繞着零個或多個名稱/值對的大括號標記 名稱是一個字符串,一個單獨的冒號令牌跟隨每個名稱,將名稱與值。一個逗號記號將一個值與下一個名稱分開。「重複名稱沒有限制。 –

+0

@DavidHammen在RFC 7159中,「對象內的名稱應該是唯一的」。這不是必須的。一些程序能夠處理它們,但有些可能不能。 –

回答

1

我認爲棘手的部分是記住密鑰是否已經擴展到數組(因爲該值可能最初是一個數組)。

因此,另一種方法是首先將所有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進行了測試。

+0

在我的回答中,我使用'resolvedConflicts'來記憶已解決的衝突。 據我所知,您的嘗試總是在'O(3n)'或更糟糕的取決於'FindMember'。我認爲,由於'Swap'函數很好,我的嘗試最好是'O(2n)',最壞的情況是'O(3n)',這取決於'std :: find'。我錯了嗎? 但現在我明白了'EraseMember'的工作原理。 –

+0

我的解決方案不需要記住已解決的密鑰,因此它可以防止內存和性能開銷。由於賦值是移動語義,所以Swap在這裏沒有優勢。對於運行時複雜度,當不考慮遞歸時,由於線性搜索,複雜度爲O(n^2)。 –

0

我完全重寫了那部分,嘗試(再次)另一種方法。我想我發現了一個很優雅的解決方案:

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,但它的作品。

如果您認爲有改進的餘地,請讓我知道在哪裏。謝謝。