0

I have a multi level JSON like in the example below. I want to write a simple code to loop through it and fetch all the keys there are in it for validation purposes.

I tried Object.keys() but that gives the keys only for the first level of the object. How do I loop it to get the whole result?

{
"id": "0001",
"ppu": 0.55,
"batters": {
    "batter": [{
            "id": "1001",
            "type": "Regular"
        },
        {
            "id": "1004",
            "type": "Devil's Food"
        }
    ]
},
"topping": {
    "id": "5001",
    "type": "None",
    "moreData": {
        "id": "5003",
        "type1": "Chocolate",
        "type2": {
            "id": "5004",
            "type": "Maple"
        }
    }
  }
}

I normally get only the first keys, i.e. "id", "ppu","batters",topping" but I want all the keys including "batter", "type", "moreData", etc. NOTE: All my keys are unique unlike the example below.

EDIT - Code that I'm trying:

function keyCheck(obj) {
    var a = Object.keys(obj);
    var arr=[];
    arr.push(a);
    for (var i = 0; i < a.length; i++) {
        var b = obj[a[i]];
        if (typeof (b) == 'object') {
            keyCheck(b);
        }   
    }    
    return arr[0];
}
keyCheck(obj);
Mads Hansen
  • 63,927
  • 12
  • 112
  • 147
Mehul
  • 148
  • 9
  • I think this is a more general JavaScript question about getting object keys recursively. There are several answers around, as well as libraries to help with this. See for instance: https://stackoverflow.com/questions/15690706/recursively-looping-through-an-object-to-build-a-property-list – grtjn Aug 26 '19 at 07:03
  • I had trouble finding a final function that is not too huge & is simple for it. – Mehul Aug 26 '19 at 07:20
  • please add expected result and what you have tried so far – Slai Aug 26 '19 at 07:50
  • Actual: [["id", "ppu", "batters", "toppings"]] Expected: [["id", "ppu", "batters", "toppings", "type", "moreData","type1", "type2"]] – Mehul Aug 26 '19 at 08:53
  • @Mehul Check my updated answer – Neel Rathod Aug 26 '19 at 09:04

5 Answers5

1

You can write a recursive method as follows,

let obj = {
 "id": "0001",
 "ppu": 0.55,
 "batters": {
  "batter": [{
   "id": "1001",
   "type": "Regular"
  },
  {
   "id": "1004",
   "type": "Devil's Food"
  }
  ]
 },
 "topping": {
  "id": "5001",
  "type": "None",
  "moreData": {
   "id": "5003",
   "type1": "Chocolate",
   "type2": {
    "id": "5004",
    "type": "Maple"
   }
  }
 }
}

function getKeys(obj, arr = []) {
 Object.entries(obj).forEach(([key, value]) => {
  if(typeof value === 'object' && !Array.isArray(value)) {
   arr.push(key);
   getKeys(value, arr);
  } else {
   arr.push(key);
  }
 });
 return arr;
}

console.log(getKeys(obj));

For old browsers

function getKeys(obj, arr = []) {
    Object.entries(obj).forEach(function(entry) {
        let key = entry[0];
        let value = entry[1]
        if(typeof value === 'object' && !Array.isArray(value)) {
            arr.push(key);
            getKeys(value, arr);
        } else {
            arr.push(key);
        }
    });
    return arr;
}
AZ_
  • 3,094
  • 1
  • 9
  • 19
  • In MarkLogic, it says Object.entries is not a function. – Mehul Aug 26 '19 at 07:16
  • you can add the [polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#Polyfill) for the same in your code. – AZ_ Aug 26 '19 at 07:22
  • I am guessing OP is running it in IE where destructuring and arrow functions are not supported too. @Mehul You can try it in a different browser – Slai Aug 26 '19 at 07:25
  • if that's so, I will remove these too. – AZ_ Aug 26 '19 at 07:26
1

You can achieve this with making a recursive function with Object.keys()

    const object = {
      id: '0001',
      ppu: 0.55,
      batters: {
        batter: [{
          id: '1001',
          type: 'Regular',
        },
        {
          id: '1004',
          type: "Devil's Food",
        },
        ],
      },
      topping: {
        id: '5001',
        type: 'None',
        moreData: {
          id: '5003',
          type1: 'Chocolate',
          type2: {
            id: '5004',
            type: 'Maple',
          },
        },
      },
    };
    
    
    function getKeyNames(obj, secondObj) {
      Object.keys(obj).forEach((r) => {
        const elem = obj[r];
        const refObj = secondObj;
        if (!Array.isArray(elem)) {
          refObj[r] = r;
        }
        if (typeof elem === 'object' && (!Array.isArray(elem))) {
          getKeyNames(elem, secondObj);
        }
      });
      return secondObj;
    }
    
    function getAllKeys(obj) {
      const secondObj = {};
      const keys = getKeyNames(obj, secondObj);
      return Object.keys(keys);
    }
    
    const result = getAllKeys(object);
    console.log(result);
Neel Rathod
  • 2,013
  • 12
  • 28
  • Why downvoted? The solution matches the OP's question – Neel Rathod Aug 26 '19 at 08:52
  • why create an object rather array for aggregation? and keeping key and value both the same in the object then return only keys? what's the point of creating a new object `const refObj = secondObj;` when objects are passed by reference also adding new key value into referenced object `refObj` but ultimately returning `secondObj`? – AZ_ Aug 26 '19 at 09:49
  • @AZ_ Because when you use `array` then duplication can be possible, whereas in ``object` keys are `unique`, And we want only `keys` in this instance, You can't create an object like this `const obj = { 'key1', 'key2', 'key3' }` so I make `object` with same key-value pair. – Neel Rathod Aug 26 '19 at 10:06
  • using objects for the unique values is not a way to go, you can use `Set`. also, you are overwriting the values of keys if the key already exists in the object. and there are other issues also I mentioned. – AZ_ Aug 26 '19 at 10:11
  • @AZ_ I create a new object `const refObj = secondObj;` Because when you configure decorators such as `ESLint` they give you a warning `no-param-reassign` if you try to make changes in a `secondObj` (Which is argument of a function), That's why I copy of an object with new variable ( `refObj`). Then whatever changes you did in `refObj`, It will replicate in main obj `secondObj`, So here I use `refObj` for indirect change in `secondObj` and return the `secondObj`. – Neel Rathod Aug 26 '19 at 10:12
  • Yes I know a new key with the same name will be overwritten in an object, But OP's also want that – Neel Rathod Aug 26 '19 at 10:15
0

You can use a recursive function to get all the keys. Below method takes a second argument unique that lets you set if keys should be duplicated in your resulting array of keys.

const allKeys = (obj, unique = true) => {
  const res = Object.entries(obj).flatMap(([k, v]) => {
    if (typeof v === 'object') {
      if (Array.isArray(v)) return [k, v.flatMap(vv => allKeys(vv, unique))].flat();
      return [k, allKeys(v, unique)].flat();
    }
    return k;
  });
  return unique ? [...new Set(res)] : res;
};


console.log(allKeys(obj));
<script>
  const obj = {
    "id": "0001",
    "ppu": 0.55,
    "batters": {
      "batter": [{
          "id": "1001",
          "type": "Regular"
        },
        {
          "id": "1004",
          "type": "Devil's Food"
        }
      ]
    },
    "topping": {
      "id": "5001",
      "type": "None",
      "moreData": {
        "id": "5003",
        "type1": "Chocolate",
        "type2": {
          "id": "5004",
          "type": "Maple"
        }
      }
    }
  };
</script>
baao
  • 71,625
  • 17
  • 143
  • 203
0

The JSON.parse reviver parameter can be used to get all key value pairs :

var keys = {}, json = '{"id":"0001","ppu":0.55,"batters":{"batter":[{"id":"1001","type":"Regular"},{"id":"1004","type":"Devil\'s Food"}]},"topping":{"id":"5001","type":"None","moreData":{"id":"5003","type1":"Chocolate","type2":{"id":"5004","type":"Maple"}}}}'

JSON.parse(json, function(key, value) { if ( isNaN(key) ) keys[key] = value })

console.log( Object.keys(keys) )
Slai
  • 22,144
  • 5
  • 45
  • 53
  • you can't return only keys. It will need all the OP's validation logic to move inside the `JSON.parse` which won't help when the validation fails or you will have to throw an error inside `parse`. also, it requires to stringify the object first. – AZ_ Aug 26 '19 at 07:35
0

So I figured out a solution by using the inputs provided by everyone here.Due to some policy I cannot post my own solution but this is what I did: Get Object.keys() for first level data. Check if it's another object or array & call the same function recursively. If not then add it to the array.

Mehul
  • 148
  • 9