Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript casts keys with numerical strings to Numbers... but Object.keys() doesn't

An external API returns a JSON result of the following form:

{
    "data": {
        "1.0": 'foo',
        "2.3": 'bar',
        "3.6": 'baz'
    }
}

Here, the keys "1.0", "2.3", "3.6" should really be taken as strings denoting a discrete categorisation, not as values along a continuous axis. It is therefore perfectly valid for this API to return these keys as strings.

However... (you can feel it coming, aren't you?)

In the JS client, I need to iterate over these keys and here comes the trouble:

  • the browser's JS engine automatically casted all these keys into Number
  • using Object.keys(myObject.data) returns... strings!
  • therefore, the following does not work at all as you can see:

let myObject = {
  "data": {
    "1.0": 'foo',
    "2.3": 'bar',
    "3.6": 'baz'
  }
}

console.log(myObject.data)
for (let k in Object.keys(myObject.data)) {
  console.log(k, myObject.data[k])
}

// {
//     1.0: 'foo',
//     2.3: 'bar',
//     3.6: 'baz
// }
// "1.0" undefined
// "2.3" undefined
// "3.6" undefined

It seems that we have two conflicting things here: first, the object keys are converted into Numbers, but at the same time, Object.keys() returns Strings instead of Numbers.

Is there an appropriate way to solve this problem?

Ideally, I'd like the object's actual keys to remain strings, as they should be. Casting the values from Object.keys() into Numbers would lead to quite cumbersome workarounds as the API can (and does) return "real" strings as keys sometimes (like { "red": 'foo', "blue": 'bar' }.

like image 340
Jivan Avatar asked Mar 17 '19 12:03

Jivan


2 Answers

Your problem is for in

for in tries to access keys in array created by Object.keys(obj.data) which is actually index

let obj = {"data": {"1.0": 'foo',"2.3": 'bar',"3.6": 'baz'}}

Object.keys(obj.data).forEach(e=>{
  console.log(typeof e)
})

//You can simply drop of Object.keys 

for (let k in obj.data) {
  console.log(k, obj.data[k])
}
like image 186
Code Maniac Avatar answered Oct 10 '22 13:10

Code Maniac


Simply do not use Object.keys:

let myObject = {
  "data": {
    "1.0": 'foo',
    "2.3": 'bar',
    "3.6": 'baz'
  }
}

console.log(myObject.data)
for (let k in myObject.data) {
  console.log(k, myObject.data[k])
}

Some explanation:

Object.keys does what it says - extracts keys from the passed in object and returns them as array (in your case that'd be: [ "1.0", "2.3", "3.6"]). So when you are trying to loop over this with for..in, you are in fact looping over that resulting array, instead of the actual object and key variable will receive an index of the corresponding item from the array (0 for "1.0", 1 for "2.3", etc). That's just how for..in works. If you would like to loop over the values of the array instead, you could use for..of as another option. Or in your case, as I've mentioned above, simply do not use Object.keys.

like image 44
jayarjo Avatar answered Oct 10 '22 11:10

jayarjo