2015-10-16 69 views
4

我正在使用angular-translate進行大型應用。有幾個人提交代碼+翻譯,很多時候翻譯對象不同步。比較JavaScript中的2個JSON對象結構

我正在構建一個Grunt插件來查看兩個文件的結構並比較它(只是鍵和整體結構,而不是值)。

的主要目標是:

  • 查找到每個文件中,並檢查是否整個對象 (或文件,在這種情況下)的結構是完全相同的作爲轉換後的那些;
  • 出錯時,返回不匹配的密鑰。

事實證明這比我預期的要複雜一點。所以我想我可以這樣做:

  1. 排序對象 ;
  2. 檢查值包含的數據的類型(因爲它們是翻譯,它將只有字符串或嵌套的對象)並將其存儲在另一個對象中,使該鍵等於原始鍵並且該值將是一個字符串'String',或者一個對象,以防它是一個對象。該對象包含子元素;
  3. 遞歸地重複步驟1-2直到整個對象被映射和排序;
  4. 對所有文件都做同樣的處理
  5. 將所有文件進行串聯和比較。

一個微小的例子是下列對象:

{ 
    key1: 'cool', 
    key2: 'cooler', 
    keyWhatever: { 
    anotherObject: { 
     key1: 'better', 
     keyX: 'awesome' 
    }, 
    aObject: 'actually, it\'s a string' 
    }, 
    aKey: 'more awesomeness' 
} 

將映射到:

{ 
    aKey: 'String', 
    key1: 'String', 
    key2: 'String', 
    keyWhatever: { 
    aObject: 'String', 
    anotherObject: { 
     key1: 'String', 
     keyX: 'String' 
    } 
    } 
} 

在此之後,我將字符串化的所有對象,並用嚴格比較進行。

我的問題是,有沒有更好的方法來執行此操作?無論是在簡單性和性能方面,因爲有很多翻譯文件,而且它們相當大。

我試圖尋找已經可以做到這一點的庫,但是我找不到任何庫。

謝謝

編輯謝謝您對賈裏德指出對象不能進行排序。我很慚愧這樣說:D另一種解決方案可能是迭代主翻譯文件中的每個屬性,並且如果它們是字符串,則將該關鍵字與其他文件進行比較。如果它們是對象,則「輸入」它們,並執行相同的操作。也許它比我的第一個猜測更簡單。應該做什麼?

+1

雖然許多實現維護*插入*順序,每個規範JavaScript對象是*無序*鍵/值對。你將不得不使用'Object.keys'並使用數組,你不能'排序'對象。 –

+0

謝謝Jared。我糾正了我的問題細節! – fgarci03

+0

這是一個很大的問題,所以我不聲稱有解決方案,但有一件事你可能沒有想到:你會如何對待它? 'root = {我:root}'(也許你會拋出一個錯誤,但是你不能使頁面永遠崩潰) – Katana314

回答

2

假設你有兩個JSON對象,jsonA和jsonB。

function compareValues(a, b) { 

    //if a and b aren't the same type, they can't be equal 
    if (typeof a !== typeof b) { 
     return false; 
    } 

    if (typeof a === 'object') { 
     var keysA = Object.keys(a).sort(), 
      keysB = Object.keys(b).sort(); 

     //if a and b are objects with different no of keys, unequal 
     if (keysA.length !== keysB.length) { 
      return false; 
     } 

     //if keys aren't all the same, unequal 
     if (!keysA.every(function(k, i) { return k === keysB[i];})) { 
      return false; 
     } 

     //recurse on the values for each key 
     return keysA.every(function(key) { 
      //if we made it here, they have identical keys 
      return compareValues(a[key], b[key]); 
     }); 

    //for primitives just use a straight up check 
    } else { 
     return a === b; 
    } 
} 

//true if their structure, values, and keys are identical  
var passed = compareValues(jsonA, jsonB); 

請注意,這可能會導致深層嵌套的JSON對象的堆棧溢出。請注意,這將適用於JSON,但不一定是常規JS對象,因爲日期對象,正則表達式等需要特殊處理。

1

實際上,您確實需要對鍵進行排序,因爲它們不需要吐出任何特定的順序。寫一個函數,

function getComparableForObject(obj) { 
    var keys = Object.keys(obj); 
    keys.sort(a, b => a > b ? 1 : -1); 

    var comparable = keys.map(
      key => key + ":" + getValueRepresentation(obj[key]) 
     ).join(","); 
    return "{" + comparable + "}"; 
} 

getValueRepresentation是一個函數,或者返回「字符串」或來電getComparableForObject。如果您擔心循環引用,請將Symbol添加到外部示波器中,repr,在上述函數中分配obj[repr] = comparable,並在getValueRepresentation中檢查每個對象是否定義了obj[repr],然後返回它而不是遞歸處理它。

1

從對象中排序鍵的數組。但是,排序的平均值爲time complexity,其值爲O(n⋅log(n))。我們可以做得更好。爲確保兩套一個快速通用算法是等價如下:

for item in B 
    if item in A 
    remove item from A 
    else 
    sets are not equivalent 
sets are equivalent iff A is empty 

爲了解決@ Katana31,因爲我們去通過維護一組訪問對象,並確保我們可以檢測到循環引用那該對象的所有後代是不是已經在列表中:

# poorly written pseudo-code 
fn detectCycles(A, found = {}) 
    if A in found 
    there is a cycle 
    else 
    found = clone(found) 
    add A to found 
    for child in A 
     detectCycles(child, found) 

下面是一個完整的實現(你可以找到一個簡化版本,它假定JSON /非圓輸入here):

var hasOwn = Object.prototype.hasOwnProperty; 
var indexOf = Array.prototype.indexOf; 

function isObjectEmpty(obj) { 
    for (var key in obj) { 
    return false; 
    } 

    return true; 
} 

function copyKeys(obj) { 
    var newObj = {}; 

    for (var key in obj) { 
    newObj[key] = undefined; 
    } 

    return newObj; 
} 

// compares the structure of arbitrary values 
function compareObjectStructure(a, b) { 
    return function innerCompare(a, b, pathA, pathB) { 
    if (typeof a !== typeof b) { 
     return false; 
    } 

    if (typeof a === 'object') { 
     // both or neither, but not mismatched 
     if (Array.isArray(a) !== Array.isArray(b)) { 
     return false; 
     } 

     if (indexOf.call(pathA, a) !== -1 || indexOf.call(pathB, b) !== -1) { 
     return false; 
     } 

     pathA = pathA.slice(); 
     pathA.push(a); 

     pathB = pathB.slice(); 
     pathB.push(b); 

     if (Array.isArray(a)) { 
     // can't compare structure in array if we don't have items in both 
     if (!a.length || !b.length) { 
      return true; 
     } 

     for (var i = 1; i < a.length; i++) { 
      if (!innerCompare(a[0], a[i], pathA, pathA)) { 
      return false; 
      } 
     } 

     for (var i = 0; i < b.length; i++) { 
      if (!innerCompare(a[0], b[i], pathA, pathB)) { 
      return false; 
      } 
     } 

     return true; 
     } 

     var map = copyKeys(a), keys = Object.keys(b); 

     for (var i = 0; i < keys.length; i++) { 
     var key = keys[i]; 

     if (!hasOwn.call(map, key) || !innerCompare(a[key], b[key], pathA, 
      pathB)) { 
      return false; 
     } 

     delete map[key]; 
     } 

     // we should've found all the keys in the map 
     return isObjectEmpty(map); 
    } 

    return true; 
    }(a, b, [], []); 
} 

請注意,此實現直接比較兩個對象的結構等同性,但不會將對象減少爲直接可比較的值(如字符串)。我沒有做過任何性能測試,但我懷疑它不會增加顯着價值,但它會消除重複確保對象不循環的需要。因此,您可以輕鬆將compareObjectStructure拆分爲兩個函數 - 一個用於比較結構,另一個用於檢查週期。