Search code examples
javascriptjavascript-objects

How to count all objects with specific key recursive?


How i can count all objects that have specific key but if object can be nested and I do not know max depth of object because everytime is differente?

for exmaple once can be like this:

{
    "coins_series": [
        {
            "series": 1,
            "coins": [
                {
                    "_id": "65c8c78845d911984d98f6be",
                    "currency": "EUR",
                    "reverse_id": "SVN200701001REG",
                    "obverse_id": [
                        "EUR199901001FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 0.01,
                    "diameter": 16.25,
                    "thickness": 1.67,
                    "weight": 2.3
                },
                {
                    "_id": "65c8c78845d911984d98f6bf",
                    "currency": "EUR",
                    "reverse_id": "SVN200701002REG",
                    "obverse_id": [
                        "EUR199901002FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 0.02,
                    "diameter": 18.75,
                    "thickness": 1.67,
                    "weight": 3.06
                },
                {
                    "_id": "65c8c78845d911984d98f6c0",
                    "currency": "EUR",
                    "reverse_id": "SVN200701005REG",
                    "obverse_id": [
                        "EUR199901005FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 0.05,
                    "diameter": 21.25,
                    "thickness": 1.67,
                    "weight": 3.92
                },
                {
                    "_id": "65c8c78845d911984d98f6c1",
                    "currency": "EUR",
                    "reverse_id": "SVN200701010REG",
                    "obverse_id": [
                        "EUR200702010FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 0.1,
                    "diameter": 19.75,
                    "thickness": 1.93,
                    "weight": 4.1
                },
                {
                    "_id": "65c8c78845d911984d98f6c2",
                    "currency": "EUR",
                    "reverse_id": "SVN200701020REG",
                    "obverse_id": [
                        "EUR200702020FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 0.2,
                    "diameter": 22.25,
                    "thickness": 2.14,
                    "weight": 5.74
                },
                {
                    "_id": "65c8c78845d911984d98f6c3",
                    "currency": "EUR",
                    "reverse_id": "SVN200701050REG",
                    "obverse_id": [
                        "EUR200702050FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 0.5,
                    "diameter": 24.25,
                    "thickness": 2.38,
                    "weight": 7.8
                },
                {
                    "_id": "65c8c78845d911984d98f6c4",
                    "currency": "EUR",
                    "reverse_id": "SVN200701100REG",
                    "obverse_id": [
                        "EUR200702100FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 1,
                    "diameter": 23.25,
                    "thickness": 2.33,
                    "weight": 7.5
                },
                {
                    "_id": "65c8c78845d911984d98f6c5",
                    "currency": "EUR",
                    "reverse_id": "SVN200701200REG",
                    "obverse_id": [
                        "EUR200702200FRO"
                    ],
                    "country": "SVN",
                    "commemorative": false,
                    "series": 1,
                    "min_year": 2007,
                    "denomination": 2,
                    "diameter": 25.75,
                    "thickness": 2.2,
                    "weight": 8.5
                }
            ]
        }
    ],
    "country": "SVN"
}

and other time can be like this:

{
    "series": 2,
    "coins": [
        {
            "_id": "65c8c78845d911984d98f724",
            "currency": "EUR",
            "reverse_id": "VAT200502001REG",
            "obverse_id": [
                "EUR199901001FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 0.01,
            "diameter": 16.25,
            "thickness": 1.67,
            "weight": 2.3
        },
        {
            "_id": "65c8c78845d911984d98f725",
            "currency": "EUR",
            "reverse_id": "VAT200502002REG",
            "obverse_id": [
                "EUR199901002FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 0.02,
            "diameter": 18.75,
            "thickness": 1.67,
            "weight": 3.06
        },
        {
            "_id": "65c8c78845d911984d98f726",
            "currency": "EUR",
            "reverse_id": "VAT200502005REG",
            "obverse_id": [
                "EUR199901005FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 0.05,
            "diameter": 21.25,
            "thickness": 1.67,
            "weight": 3.92
        },
        {
            "_id": "65c8c78845d911984d98f727",
            "currency": "EUR",
            "reverse_id": "VAT200502010REG",
            "obverse_id": [
                "EUR199901010FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 0.1,
            "diameter": 19.75,
            "thickness": 1.93,
            "weight": 4.1
        },
        {
            "_id": "65c8c78845d911984d98f728",
            "currency": "EUR",
            "reverse_id": "VAT200502020REG",
            "obverse_id": [
                "EUR199901020FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 0.2,
            "diameter": 22.25,
            "thickness": 2.14,
            "weight": 5.74
        },
        {
            "_id": "65c8c78845d911984d98f729",
            "currency": "EUR",
            "reverse_id": "VAT200502050REG",
            "obverse_id": [
                "EUR199901050FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 0.5,
            "diameter": 24.25,
            "thickness": 2.38,
            "weight": 7.8
        },
        {
            "_id": "65c8c78845d911984d98f72a",
            "currency": "EUR",
            "reverse_id": "VAT200502100REG",
            "obverse_id": [
                "EUR199901100FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 1,
            "diameter": 23.25,
            "thickness": 2.33,
            "weight": 7.5
        },
        {
            "_id": "65c8c78845d911984d98f72b",
            "currency": "EUR",
            "reverse_id": "VAT200502200REG",
            "obverse_id": [
                "EUR199901200FRO"
            ],
            "country": "VAT",
            "commemorative": false,
            "series": 2,
            "min_year": 2005,
            "max_year": 2005,
            "denomination": 2,
            "diameter": 25.75,
            "thickness": 2.2,
            "weight": 8.5
        }
    ]
}

some other time can be different format. I need to count all objects with commemorative key each time without specific data structure.


Solution

  • You can recursively iterate arrays and objects values and check that the objects contain your key:

    const countKey = (obj, key) => {
      let count = 0;
      if(Array.isArray(obj)){
        for(const item of obj){
          count += countKey(item, key);
        }
      } else if (obj && typeof obj === 'object'){
         for(const k in obj){
          if(k === key) count++;
          count += countKey(obj[k], key);
        }
      }
      return count;
    };
        
    
    // Example
    const data = {"coins_series": [{"series": 1,"coins": [{"_id": "65c8c78845d911984d98f6be","currency": "EUR","reverse_id": "SVN200701001REG","obverse_id": ["EUR199901001FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.01,"diameter": 16.25,"thickness": 1.67,"weight": 2.3},{"_id": "65c8c78845d911984d98f6bf","currency": "EUR","reverse_id": "SVN200701002REG","obverse_id": ["EUR199901002FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.02,"diameter": 18.75,"thickness": 1.67,"weight": 3.06},{"_id": "65c8c78845d911984d98f6c0","currency": "EUR","reverse_id": "SVN200701005REG","obverse_id": ["EUR199901005FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.05,"diameter": 21.25,"thickness": 1.67,"weight": 3.92},{"_id": "65c8c78845d911984d98f6c1","currency": "EUR","reverse_id": "SVN200701010REG","obverse_id": ["EUR200702010FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.1,"diameter": 19.75,"thickness": 1.93,"weight": 4.1},{"_id": "65c8c78845d911984d98f6c2","currency": "EUR","reverse_id": "SVN200701020REG","obverse_id": ["EUR200702020FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.2,"diameter": 22.25,"thickness": 2.14,"weight": 5.74},{"_id": "65c8c78845d911984d98f6c3","currency": "EUR","reverse_id": "SVN200701050REG","obverse_id": ["EUR200702050FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.5,"diameter": 24.25,"thickness": 2.38,"weight": 7.8},{"_id": "65c8c78845d911984d98f6c4","currency": "EUR","reverse_id": "SVN200701100REG","obverse_id": ["EUR200702100FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 1,"diameter": 23.25,"thickness": 2.33,"weight": 7.5},{"_id": "65c8c78845d911984d98f6c5","currency": "EUR","reverse_id": "SVN200701200REG","obverse_id": ["EUR200702200FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 2,"diameter": 25.75,"thickness": 2.2,"weight": 8.5}]}],"country": "SVN"};
    const count = countKey(data, "commemorative");
    console.log(count); // 8

    And benchmarking:

    ` Chrome/121
    ------------------------------------------------------------------------------------
    >                     n=1       |      n=10       |     n=100      |     n=1000     
    Alexander       1.00x   x1m 757 | 1.00x x100k 747 | 1.00x x10k 751 |  1.00x  x1k 736
    trincot         3.00x x100k 227 | 2.93x  x10k 219 | 2.97x  x1k 223 |  2.99x x100 220
    test30 (JSON)   8.40x x100k 636 | 8.09x  x10k 604 | 9.17x  x1k 689 | 10.98x x100 808
    ------------------------------------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    
    ` Firefox/122
    ----------------------------------------------------------------------------------
    >                     n=1       |      n=10      |     n=100      |     n=1000    
    Alexander       1.00x x100k 123 | 1.00x x10k  96 | 1.00x x10k 944 | 1.00x x100 101
    trincot         4.07x x100k 501 | 5.47x x10k 525 | 5.74x  x1k 542 | 5.24x x100 529
    test30 (JSON)   7.39x x100k 909 | 9.11x x10k 875 | 9.43x  x1k 890 | 9.13x x100 922
    ----------------------------------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    

    // Example
    const $chunk = [{"coins_series": [{"series": 1,"coins": [{"_id": "65c8c78845d911984d98f6be","currency": "EUR","reverse_id": "SVN200701001REG","obverse_id": ["EUR199901001FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.01,"diameter": 16.25,"thickness": 1.67,"weight": 2.3},{"_id": "65c8c78845d911984d98f6bf","currency": "EUR","reverse_id": "SVN200701002REG","obverse_id": ["EUR199901002FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.02,"diameter": 18.75,"thickness": 1.67,"weight": 3.06},{"_id": "65c8c78845d911984d98f6c0","currency": "EUR","reverse_id": "SVN200701005REG","obverse_id": ["EUR199901005FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.05,"diameter": 21.25,"thickness": 1.67,"weight": 3.92},{"_id": "65c8c78845d911984d98f6c1","currency": "EUR","reverse_id": "SVN200701010REG","obverse_id": ["EUR200702010FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.1,"diameter": 19.75,"thickness": 1.93,"weight": 4.1},{"_id": "65c8c78845d911984d98f6c2","currency": "EUR","reverse_id": "SVN200701020REG","obverse_id": ["EUR200702020FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.2,"diameter": 22.25,"thickness": 2.14,"weight": 5.74},{"_id": "65c8c78845d911984d98f6c3","currency": "EUR","reverse_id": "SVN200701050REG","obverse_id": ["EUR200702050FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 0.5,"diameter": 24.25,"thickness": 2.38,"weight": 7.8},{"_id": "65c8c78845d911984d98f6c4","currency": "EUR","reverse_id": "SVN200701100REG","obverse_id": ["EUR200702100FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 1,"diameter": 23.25,"thickness": 2.33,"weight": 7.5},{"_id": "65c8c78845d911984d98f6c5","currency": "EUR","reverse_id": "SVN200701200REG","obverse_id": ["EUR200702200FRO"],"country": "SVN","commemorative": false,"series": 1,"min_year": 2007,"denomination": 2,"diameter": 25.75,"thickness": 2.2,"weight": 8.5}]}],"country": "SVN"}];
    
    const $input = [];
    
    // @benchmark test30 (JSON)
    {
    let count = 0
    
    JSON.stringify($input, (key, value) => {
      if (key === 'commemorative') count++;
      return value;
    })
    count;
    }
    
    // @benchmark trincot
    const countKey = (obj, key) =>
        Object(obj) !== obj ? 0  // A primitive value (base case)
           // A boolean indicating a match is coerced to 0 or 1:
        : (!Array.isArray(obj) && Object.hasOwn(obj, key)) +  
            // Recursive calls
            Object.values(obj).reduce((sum, obj) => sum + countKey(obj, key), 0);
    
    countKey($input, "commemorative");
    
    // @benchmark Alexander
    {
    const countKey = (obj, key) => {
      let count = 0;
      if(Array.isArray(obj)){
        for(const item of obj){
          count += countKey(item, key);
        }
      } else if (obj && typeof obj === 'object'){
         for(const k in obj){
          if(k === key) count++;
          count += countKey(obj[k], key);
        }
      }
      return count;
    };
    countKey($input, "commemorative");
    }
    
    /*@end*/eval(atob('e2xldCBlPWRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3Rvcigic2NyaXB0Iik7aWYoIWUubWF0Y2hlcygiW2JlbmNobWFya10iKSl7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7dC5zcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9zaWxlbnRtYW50cmEvYmVuY2htYXJrL2xvYWRlci5qcyIsdC5kZWZlcj0hMCxkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHQpfX0='));