122

Assume I have an object:

var obj = {
  foo:"bar",
  fizz:"buzz"
};

I need to access a property of that object dynamically like so:

var objSetter = function(prop,val){
  obj[prop] = val;
}

No problems there, except for that prop needs to be case insensitive in case the property name is passed into the function as, say, Foo instead of foo.

So how can I point to an object's property by name without regard to case? I would like to avoid iterating the entire object if possible.

Dexygen
  • 12,287
  • 13
  • 80
  • 147
Matt Cashatt
  • 23,490
  • 28
  • 78
  • 111
  • 8
    You can't. The language is case sensitive. Think about `obj = {foo: true, Foo: false}` – user123444555621 Sep 18 '12 at 20:24
  • 1
    Always keep your actual property names as all lower-case (or upper), and then convert when querying. – Pointy Sep 18 '12 at 20:25
  • if you expect "foo" instead of "Foo", you may convert `prop` to lower before using it. – Claudio Redi Sep 18 '12 at 20:25
  • 3
    Look at JavaScript proxy objects to implement was is effectively a means of changing a wildcard getter: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy – Zach Smith Sep 26 '17 at 08:42
  • 3
    Do NOT do this. Stop providing mechanisms to allow sloppy development. Keys are case sensitive. If you allow someone else to use a key with different case then you are allowing them to create crappy code that is difficult to maintain, not to mention slower since you have to run special code to allow the keys to match when they should not. – Intervalia Aug 16 '19 at 20:35
  • Related: [Are javascript object keys case-sensitive?](https://stackoverflow.com/q/42400548/1048572), [Is the hasOwnProperty method in JavaScript case sensitive?](https://stackoverflow.com/q/5832888/1048572), [Can an Object have case-insensitive member access?](https://stackoverflow.com/q/57530678/1048572) – Bergi Dec 14 '20 at 21:28
  • 3
    @Intervalia Except that certain things are case-insensitive regardless of how you personally feel about it. E.g., if I want to check HTTP headers, they are case insensitive. I would greatly prefer that they were not, but the fact of the matter is that they are. Feel free to take it up with w3. – Layne Bernardo Feb 26 '21 at 01:50
  • @LayneBernardo If you know that things are case insensitive, like HTTP headers, then I suggest creating a second set of "normalized" headers and make them all lower case and then make your code lower case the header name coming into your function. The point is that the language you are using has specific rules. Follow them. If the integration point has other rules then find a way to correct them in your code. – Intervalia Feb 26 '21 at 16:31

20 Answers20

80

Try this:

var myObject = { "mIxeDCaSEKeY": "value" };

var searchKey = 'mixedCaseKey';
var asLowercase = searchKey.toLowerCase();
myObject[Object.keys(myObject).find(key => key.toLowerCase() === asLowercase)];

You can alternatively already provide the searchKey in lowercase.

If you want it as a function:

/**
  * @param {Object} object
  * @param {string} key
  * @return {any} value
 */
function getParameterCaseInsensitive(object, key) {
  const asLowercase = key.toLowerCase();
  return object[Object.keys(object)
    .find(k => k.toLowerCase() === asLowercase)
  ];
}

If the key can't be found, then it'll return undefined, just like normal.

If you need to support older browsers, then you can use filter instead:

function getParameterCaseInsensitive(object, key) {
  const asLowercase = key.toLowercase();
  return object[Object.keys(object).filter(function(k) {
    return k.toLowerCase() === asLowercase;
  })[0]];
}

I suggest using the polyfills for Object.keys() and Array.filter() if you need even older support.


Note: If you want to also check non-enumerable keys, use Object.getOwnPropertyNames() instead of Object.keys().

Nerdy Note: This assumes your Object doesn't have a key undefined (eg: const foo = {[undefined]: 'bar'};). That's just weird.

Gazihan Alankus
  • 11,256
  • 7
  • 46
  • 57
ShortFuse
  • 5,970
  • 3
  • 36
  • 36
  • I was curious, what documentation comments style is from. Then I found https://jsdoc.app/about-getting-started.html – Michael Freidgeim May 29 '20 at 08:26
  • 1
    TypeScript will also natively read JSDocs and perform type checking. It's built right into VSCode which makes life easier. – ShortFuse May 29 '20 at 14:17
29

Compare all the properties of obj with prop.

var objSetter = function(prop,val){
  prop = (prop + "").toLowerCase();
  for(var p in obj){
     if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
           obj[p] = val;
           break;
      }
   }
}
Anoop
  • 23,044
  • 10
  • 62
  • 76
  • 3
    Thanks. This will certainly work, but I am hoping to avoid iterating over entire objects. I will edit my post to make that more clear. Thanks again. – Matt Cashatt Sep 18 '12 at 20:33
  • Thanks Shusl, looks like an iteration is my only option. Also, can you please tell me what the `+ ""` is for? Is this just for string conversion? – Matt Cashatt Sep 18 '12 at 20:38
  • 1
    Making sure that toLowerCase is being applied on string. Eg. (1 + "") will convert 1 to "1". – Anoop Sep 18 '12 at 20:47
  • 17
    There is no need to coerce `p` to string, ECMA-262 specifies Object properties as strings, any user agent or host that was non–compliant would have serious problems. There is also no need for `break`, you should use `return`. – RobG Sep 18 '12 at 21:05
  • 3
    I tried this on an object with over 1 million keys and it is intolerably slow. A better approach would be to use a proxy object (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). Or just add keys to the dictionary as upper case to begin with if your use-case allows that – Zach Smith Sep 26 '17 at 08:38
13

For this, I prefer using the prototype over a standalone function just for ease of use and expressiveness. I just don't like funneling objects into functions if I don't have to.

Also, while the accepted answer works, I wanted a more comprehensive solution for both getting and setting that would behave as much like the native dot notation or bracket notation as possible.

With that in mind, I created a couple prototype functions for setting/getting an object property without regard to case. You have to remember to be VERY responsible when adding to the Object prototype. Especially when using JQuery and other libraries. Object.defineProperty() with enumerable set to false was used specifically to avoid conflict with JQuery. I also didn't bother naming the functions anything that indicates they are case-insensitive, but you certainly could. I like shorter names.

Here's the getter:

Object.defineProperty(Object.prototype, "getProp", {
    value: function (prop) {
        var key,self = this;
        for (key in self) {
            if (key.toLowerCase() == prop.toLowerCase()) {
                return self[key];
            }
        }
    },
    //this keeps jquery happy
    enumerable: false
});

Here's the setter:

Object.defineProperty(Object.prototype, "setProp", {
    value: function (prop, val) {
        var key,self = this;
        var found = false;
        if (Object.keys(self).length > 0) {
            for (key in self) {
                if (key.toLowerCase() == prop.toLowerCase()) {
                    //set existing property
                    found = true;                        
                    self[key] = val;
                    break;
                }
            }
        }

        if (!found) {
            //if the property was not found, create it
            self[prop] = val;
        }  

        return val;
    },
    //this keeps jquery happy
    enumerable: false
});

Now that we've created those functions, our code is super clean and concise and just works.

Case-insensitive getting:

var obj = {foo: 'bar', camelCase: 'humpy'}

obj.getProp("FOO");          //returns 'bar'
obj.getProp("fOO");          //returns 'bar'
obj.getProp("CAMELCASE");    //returns 'humpy' 
obj.getProp("CamelCase");    //returns 'humpy'

Case-insensitive setting:

var obj = {foo: 'bar', camelCase: 'humpy'}

obj.setProp('CAmelCasE', 'super humpy');     //sets prop 'camelCase' to 'super humpy'
obj.setProp('newProp', 'newval');      //creates prop 'newProp' and sets val to 'newval'  
obj.setProp('NewProp', 'anotherval');  //sets prop 'newProp' to 'anotherval'
Matthew Goodwin
  • 1,162
  • 12
  • 16
  • 2
    Of course this will fail horribly once someone does `obj.setProp("GETPROP")` – Bergi Apr 23 '20 at 18:50
  • @bergi I actually don't think that would cause an issue since these are properties on the Object prototype. What you are doing with `obj.setProp("GETPROP")` would literally just set a property on an object with a key of `GETPROP`. It shouldn't interfere with the Object prototype. – Matthew Goodwin May 18 '22 at 19:27
7

Yet another variation on those already presented which pushes the iteration down into the Underscore/Lodash findKey function:

var _ = require('underscore');
var getProp = function (obj, name) {
    var realName = _.findKey(obj, function (value, key) {
        return key.toLowerCase() === name.toLowerCase();
    });
    return obj[realName];
};

For example:

var obj = { aa: 1, bB: 2, Cc: 3, DD: 4 };
getProp(obj, 'aa'); // 1
getProp(obj, 'AA'); // 1
getProp(obj, 'bb'); // 2
getProp(obj, 'BB'); // 2
getProp(obj, 'cc'); // 3
getProp(obj, 'CC'); // 3
getProp(obj, 'dd'); // 4
getProp(obj, 'DD'); // 4
getProp(obj, 'EE'); // undefined
Rusty Shackleford
  • 1,111
  • 2
  • 14
  • 19
  • How does the `underscore` library implement this? I.e. I'm looking for a way that doesn't involve a linear search of some object's keys – Zach Smith Sep 26 '17 at 08:40
  • 2
    @ZachSmith It's O(n); see [here](https://github.com/jashkenas/underscore/blob/e944e0275abb3e1f366417ba8facb5754a7ad273/underscore.js#L1093-L1101). – Rusty Shackleford Sep 26 '17 at 17:23
3

It seems to me like a good candidate for Proxy with traps to convert string keys to either upper case or lower case and behaving like a regular object. This works with either notation: dots or braquets

Here is the code:

'use strict';

function noCasePropObj(obj)
{
 var handler =
 {
  get: function(target, key)
   {
    //console.log("key: " + key.toString());
    if (typeof key == "string")
    {
     var uKey = key.toUpperCase();

     if ((key != uKey) && (key in target))
      return target[key];
     return target[uKey];
    }
    return target[key];
   },
  set: function(target, key, value)
   {
    if (typeof key == "string")
    {
     var uKey = key.toUpperCase();

     if ((key != uKey) && (key in target))
      target[key] = value;
     target[uKey] = value;
    }
    else
     target[key] = value;
   },
  deleteProperty: function(target, key)
   {
    if (typeof key == "string")
    {
     var uKey = key.toUpperCase();

     if ((key != uKey) && (key in target))
      delete target[key];
     if (uKey in target)
      delete target[uKey];
    }
    else
     delete target[key];
   },
 };
 function checkAtomic(value)
 {
  if (typeof value == "object")
   return new noCasePropObj(value); // recursive call only for Objects
  return value;
 }

 var newObj;

 if (typeof obj == "object")
 {
  newObj = new Proxy({}, handler);
        // traverse the Original object converting string keys to upper case
  for (var key in obj)
  {
   if (typeof key == "string")
   {
    var objKey = key.toUpperCase();

    if (!(key in newObj))
     newObj[objKey] = checkAtomic(obj[key]);
   }
  }
 }
 else if (Array.isArray(obj))
 {
        // in an array of objects convert to upper case string keys within each row
  newObj = new Array();
  for (var i = 0; i < obj.length; i++)
   newObj[i] = checkAtomic(obj[i]);
 }
 return newObj; // object with upper cased keys
}

// Use Sample:
var b = {Name: "Enrique", last: "Alamo", AdDrEsS: {Street: "1233 Main Street", CITY: "Somewhere", zip: 33333}};
console.log("Original: " + JSON.stringify(b));  // Original: {"Name":"Enrique","last":"Alamo","AdDrEsS":{"Street":"1233 Main Street","CITY":"Somewhere","zip":33333}}
var t = noCasePropObj(b);
console.log(JSON.stringify(t)); // {"NAME":"Enrique","LAST":"Alamo","ADDRESS":{"STREET":"1233 Main Street","CITY":"Somewhere","ZIP":33333}}
console.log('.NaMe:' + t.NaMe); // .NaMe:Enrique
console.log('["naME"]:' + t["naME"]); // ["naME"]:Enrique
console.log('.ADDreSS["CitY"]:' + t.ADDreSS["CitY"]); // .ADDreSS["CitY"]:Somewhere
console.log('check:' + JSON.stringify(Object.getOwnPropertyNames(t))); // check:["NAME","LAST","ADDRESS"]
console.log('check2:' + JSON.stringify(Object.getOwnPropertyNames(t['AddresS']))); // check2:["STREET","CITY","ZIP"]
3

This answer requires ES6.

const x = { 'aB': 1, 'X-Total-Count': 10, y3: 2 }
console.log(x[Object.keys(x).find(key=>{return key.match(/^ab$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^x-total-count$/i)})])
console.log(x[Object.keys(x).find(key=>{return key.match(/^y3$/i)})])
Collin Thomas
  • 1,590
  • 1
  • 13
  • 12
  • 1
    Your example is flawed, your syntax for reduce is incorrect. Add another key to your object, and your example will break, for example: ```js const x = {'first':5,'X-Total-Count':10,'another':20}; console.log(x[Object.keys[x].reduce(key=>{return key.match(/x-total-count/i)})]); ``` This will fail because the first parameter of reduce is the total, not the value, and the value will be returned as null for the first key and on the second time through you will be trying to do .match on null which will cause an error. I will post a corrected example as an answer. – Dan Willett Dec 02 '20 at 19:17
  • 1
    Thanks for pointing that out, I'm not sure why I used reduce. I updated my answer using find() and provided multiple tests. – Collin Thomas Dec 02 '20 at 22:22
  • How about adding anchors (^$) to the regex to get an exact match? – Johan Maes Sep 22 '21 at 10:19
  • @JohanMaes even better – Collin Thomas Sep 23 '21 at 00:21
1

You could do this in order to "normalize" prop

 var normalizedProp = prop.toLowerCase();
 obj[normalizedProp] = val;
Claudio Redi
  • 67,454
  • 15
  • 130
  • 155
  • 1
    Thanks. I see how to cast `prop` to lowercase, but what if I can't guarantee that the *actual* property name will be all lowercase? Maybe I am missing something? – Matt Cashatt Sep 18 '12 at 20:32
  • 1
    @MatthewPatrickCashatt You're not missing anything: there is nothing to miss -- because there is nothing in the language to help here :) –  Sep 18 '12 at 20:39
  • 6
    Reason for the down vote: This doesn't answer the question as it assumes, like @RobG's answer, that property is lowercase. – Matthew Goodwin Oct 16 '15 at 17:09
1
const getPropertyNoCase = (obj, prop) => obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];

or

const getPropertyNoCase = (obj, prop) => {
    const lowerProp = prop.toLowerCase(obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
}
Yaron Pdut
  • 41
  • 2
1

The ES6 example posted by @nilloc is incorrect and will break in use.

Here is a working example:

const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
   if (!result) {
      return key.match(/x-total-count/i)
   } else {
      return result;
   }
 },null)]);

or better yet, it should return undefined if the key doesn't exist:

const x = {'first':5,'X-Total-Count':10,'third':20};
console.log(x[Object.keys(x).reduce((result,key)=>{
   if (!result) {
     return key.match(/x-total-count/i) || undefined
   } else {
     return result;
   }
  },undefined)]);

One consideration is that the above example will return the last matching key in the object if there are multiple keys that match.

Here is an example with the code made into a function:

/**
  * @param {Object} object
  * @param {string} key
  * @return {string||undefined} value || undefined
 */
function getKeyCase(obj,key) {
  const re = new RegExp(key,"i");
  return Object.keys(obj).reduce((result,key)=>{
   if (!result) {
     return key.match(re) || undefined
   } else {
     return result;
   }
  },undefined);

const x = {'first':5,'X-Total-Count':10,'third':20};

console.log(x[getKeyCase(x,"x-total-count")]);
Dan Willett
  • 945
  • 2
  • 10
  • 20
1

Its really sad that the iteration can't be skipped as it seems. For me what is acceptable but may not be for everyone is to shape the object one time via iteration and then use it in regular hashmap fashion.

const hashmap = {
  'FOO': 'foo as in function programming',
  'bar': 'bar is in baz',
};

const shapedmap = Object.entries(hashmap).reduce(
  (acc, [key, val]) => (acc[key.toUpperCase()] = val, acc), {}
);

for (const term of ['foo', 'bar', 'baz']) {
  const match = shapedmap[term.toUpperCase()]
  match && console.log('awesome, we got the term.', match);
};

Even if it just one time lookup has to be performed, it shouldn't less performant as any other iteration solution since after 1 pass, the lookup speed is constant. (I guess).

The Fool
  • 16,715
  • 5
  • 52
  • 86
  • 2
    There's one case where this style has a material advantage: if you're setting multiple properties at once, and reuse the hashmap. Otherwise you're right, it's still O(n) time, like the other solutions... the only other difference I can think of between this and the other linear iterating solutions is *fast-bail*; this does the full iteration up front, which prevents bailing early if a match is found mid-way through. – zcoop98 Jul 14 '21 at 21:13
1

This will convert everything to lowercase, but in a bind this could help if you are not concerned with retaining case.

var somedata = {
    "MixEdCase": 1234
}

var temp = JSON.parse(JSON.stringify(somedata).toLowerCase());

console.log(temp.mixedcase);
// or
console.log(temp["mixedcase"]);
1

This is an old question, but it was the first one I found. As @ZachSmith says, you can use a Proxy. Here's some example code:

function lowercase(oldKey) {
    // Check that it's a string.
    return typeof oldKey === 'string' ? oldKey.toLowerCase() : oldKey;
}
const propertiesMap = new Map(
    Object.keys(obj).map(propKey => [lowercase(propKey), obj[propKey]])
);
const caseInsensitiveGetHandler = {
    get: function(target, property, receiver) {
        return propertiesMap.get(lowercase(property));
    }
};
obj = new Proxy(obj, caseInsensitiveGetHandler);

For my use case, I only needed to proxy the object's getter, but you may need to implement more of the Proxy methods.

whistling_marmot
  • 3,561
  • 3
  • 25
  • 39
0

There is no need for any iteration. Since prop might not be a string, it should be coerced to a string first where appropriate since that's what objects do natively. A simple getter function is:

function objGetter(prop) {
  return obj[String(prop).toLowerCase()];
}

If there is a requirement is to restring access to own properties:

function objGetter(prop) {
  prop = String(prop).toLowerCase();

  if (obj.hasOwnProperty(prop)) {
    return obj.prop;
  }
}

and a setter:

function objSetter(prop, val) {
  obj[String(prop).toLowerCase()] = val;
}
RobG
  • 142,382
  • 31
  • 172
  • 209
  • 4
    This assumes all properties on the source object are lower-case. – Chris Haines Sep 18 '15 at 06:44
  • 1
    Agree with @Hainesy, this assumes case, which is contrary to what the question was getting at. – Matthew Goodwin Oct 16 '15 at 17:05
  • @MattGoodwin—you seem to have missed the setter, which creates the property with a lower case name. – RobG Oct 16 '15 at 23:38
  • 2
    @RobG My issue with this answer is that given an object with indeterminately cased property names, your solution would not retrieve the value unless the properties themselves were initially created using your methods or the properties happened to be lowercase. What I believe the questioner was asking for was a way to access object properties when the case of said properties is not known ahead of time. – Matthew Goodwin Oct 19 '15 at 15:23
  • @MattGoodwin, re-read the author's question. He doesn't imply he doesn't have control over the object. The question is specific about the case of `prop` that's passed into the objSetter() function. In fact, the questioners inclusion of the objSetter() function in the first place strongly implies the author is in control of creating the object. An answer that states the property names must be normalized to lowercase and provides assitance in doing is a reasonable answer. If the author needs more beyond that, then the author can clarify in a comment or by editing the question. – bobpaul Oct 10 '20 at 20:04
  • @MattGoodwin—the OP says twice that they require it to be case insensitive. Using *toLowerCase* (or *toUpperCase*) does that. – RobG Oct 11 '20 at 13:51
0

Heres a very simple code to do this Assuming that data is the array of objects like

data=[{"A":"bc","B":"nn"}]

var data=data.reduce(function(prev, curr) {
    var cc = curr; // current value
    var K = Object.keys(cc); // get all keys
    var n = {};
    for (var i = 0; i < K.length; i++) {
        var key = K[i];//get hte key

        n[key.toLowerCase()] = cc[key] // convert to lowercase and assign 
    }
    prev.push(n) // push to array
    return prev;
}, [])

Output will be

data=[{"a":"bc","b":"nn"}]
prajnavantha
  • 1,111
  • 13
  • 17
0

You might only need to do case-insensitive matching (usually expensive because of object iteration) IF a case-sensitive match (cheap and quick) fails.

Say you have:

var your_object = { "Chicago" : 'hi' , "deTroiT" : 'word' , "atlanta" : 'get r dun' } ;

And you have, for whatever reason, the_value, Detroit:

if( your_object.hasOwnProperty( the_value ) ) 
  { 
    // do what you need to do here
  } 
else  
  { // since the case-sensitive match did not succeed, 
    //   ... Now try a the more-expensive case-insensitive matching

    for( let lvs_prop in your_object ) 
      { if( the_value.toLowerCase()  == lvs_prop.toLowerCase() ) 
          { 

            // do what you need to do here

            break ;
          } ;
      } 
  } ;
dsdsdsdsd
  • 2,880
  • 6
  • 41
  • 56
0

why would we do it that complicated when we simply can make it all lower case:

    var your_object = { 
"chickago" : 'hi' ,
 "detroit" : 'word', 
 "atlanta" : 'get r dun',     
GetName: function (status) {
        return this[status].name;
    } };

to call it: your_object.GetName(your_var.toLowerCase());

KaiOsmon
  • 19
  • 3
  • 8
    This assumes that you have control over the object creation and that the keys in the object are lower case – velop Dec 14 '18 at 11:23
0

Another simple way:

function getVal(obj, prop){
var val;
  prop = (prop + "").toLowerCase();
  for(var p in obj){
     if(obj.hasOwnProperty(p) && prop == (p+ "").toLowerCase()){
           val = obj[p]
           break;
      }
   }
   return val;
}

Use it like this:

var obj = {
  foo:"bar",
  fizz:"buzz"
};
    getVal(obj,"FoO") -> returns "bar"
GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
0

Here is a nice recursive function that allows you to traverse a javascript object in a case-insensitive way:

let testObject = {'a': {'B': {'cC': [1,2,3]}}}
let testSeq = ['a','b','cc']

function keySequence(o, kseq) {
  if(kseq.length==0){ return o; }
  let validKeys = Object.keys(o).filter(k=>k.toLowerCase()==kseq[0].toLowerCase());
  if(validKeys.length==0) { return `Incorrect Key: ${kseq[0]}` }
  return keySequence(o[validKeys[0]], kseq.slice(1))
}

keySequence(testObject, testSeq); //returns [1,2,3]
cbartondock
  • 663
  • 6
  • 17
0

So, you will need to get the object key that matches the case of the existing object, then use this to do your object update.

const obj = {
  foo:"bar",
  fizz:"buzz"

};

// to get obj.foo or obj.FOO or obj.foO returning "bar"
// create regex expression of case insensitive version of the key string
const regex=passedKey=> new RegExp(`^${passedKey}$`,'gi');
   // find the key that matches the string you are passing
const formattedKey=passedKey=>Object.keys(obj).find(key=>regex(passedKey).test(key));

formattedKey('Foo'); // returns foo
formattedKey('FoO'); // returns foo

// consequently you can  can use it like wise
obj[formattedKey('Foo')] // returns bar
obj[formattedKey('FoO')] // returns bar
obj[formattedKey('foo')] // returns bar
Tony Melek
  • 61
  • 1
  • 3
0
const getProperty = (obj, prop) => Object.entries(obj).find(([key]) => key.toLowerCase() === prop.toLowerCase())?.[1]
Fareed Alnamrouti
  • 30,771
  • 4
  • 85
  • 76