Доступ до властивості JavaScript не враховує регістр?


80

Припустимо, у мене є об’єкт:

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

Мені потрібно отримати доступ до властивості цього об’єкта динамічно так:

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

Проблем тут немає, за винятком того, що це propмає бути чутливим до регістру, якщо ім'я властивості передається у функцію як, скажімо, Fooзамість foo.

То як я можу вказати на властивість об’єкта за назвою, не враховуючи регістр? Я хотів би уникати ітерації всього об’єкта, якщо це можливо.


7
Ви не можете. Мова чутлива до регістру. Подумайте проobj = {foo: true, Foo: false}
user123444555621

Завжди зберігайте фактичні назви властивостей як усі малі (або великі), а потім перетворюйте їх під час запитів.
Пойнті

якщо ви очікуєте "foo" замість "Foo", ви можете перетворити propна нижчий, перш ніж використовувати його.
Клаудіо Реді,

1
Погляньте на об’єкти проксі JavaScript, які потрібно реалізувати, - це фактично засіб зміни підстановочного символу: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Зак Сміт

Не роби цього. Припиніть надавати механізми, що дозволяють недбалий розвиток. Клавіші чутливі до регістру. Якщо ви дозволяєте комусь іншому використовувати ключ з іншим регістром, тоді ви дозволяєте йому створювати глупо код, який важко підтримувати, не кажучи вже про повільніше, оскільки вам потрібно запустити спеціальний код, щоб дозволити клавішам збігатися, коли вони не повинні.
Інтервалія,

Відповіді:


28

Порівняйте всі властивості obj з 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;
      }
   }
}

3
Дякую. Це, безумовно, спрацює, але я сподіваюсь уникати ітерації по цілих об'єктах. Я відредагую свою публікацію, щоб зробити це більш чітким. Знову дякую.
Matt Cashatt

Завдяки Шуслі, схоже, ітерація - це мій єдиний варіант. Також, будь ласка, скажіть мені, для чого + ""це? Це лише для перетворення рядків?
Matt Cashatt

1
Переконавшись, що toLowerCase застосовується до рядка. Напр. (1 + "") перетворить 1 на "1".
Ануп,

16
Немає необхідності примушувати pдо використання рядка, ECMA-262 визначає властивості об’єкта як рядки, будь-який користувальницький агент або хост, який не відповідає вимогам, матиме серйозні проблеми. Також немає необхідності break, ви повинні використовувати return.
RobG

2
Я спробував це на об'єкті з понад 1 мільйоном ключів, і це нестерпно повільно. Кращим підходом було б використовувати проксі-об'єкт ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Або просто додайте ключі до словника як великі регістри, щоб почати з того, якщо ваш варіант використання це дозволяє
Зак Сміт

44

Спробуйте це:

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

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

В якості альтернативи ви вже можете вказати searchKey з малої літери.

Якщо ви хочете це як функцію:

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

Якщо об'єкт не вдається знайти, він повернеться невизначеним, як звичайно.

Якщо вам потрібна підтримка старих браузерів, ви можете filterзамість них використовувати :

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

Я пропоную використовувати polyfills для Object.keys () та Array.filter (), якщо вам потрібна ще старша підтримка.


Мені було цікаво, з якого стилю коментування документації. Тоді я знайшов jsdoc.app/about-getting-started.html
Michael Freidgeim

1
TypeScript також буде спочатку читати JSDocs і виконувати перевірку типу. Він вбудований прямо у VSCode, що полегшує життя.
ShortFuse

10

Для цього я віддаю перевагу використанню прототипу над окремою функцією лише для зручності використання та виразності. Я просто не люблю перетворювати об’єкти на функції, якщо цього не потрібно.

Крім того, хоча прийнята відповідь працює, я хотів отримати більш комплексне рішення як для отримання, так і для налаштування, яке б поводилось якомога більше, як нотація крапкових позначень або нотація дужок.

Маючи це на увазі, я створив кілька прототипів функцій для встановлення / отримання властивості об’єкта без урахування регістру. Ви повинні пам’ятати про ДУЖЕ відповідальність при додаванні до прототипу об’єкта. Особливо при використанні JQuery та інших бібліотек. Object.defineProperty () з переліченим значенням false було використано спеціально, щоб уникнути конфлікту з JQuery. Я також не турбувався називати функції тим, що вказує на те, що вони не враховують регістр, але ви, звичайно, могли б. Мені подобаються коротші імена.

Ось геттер:

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
});

Ось сетер:

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
});

Тепер, коли ми створили ці функції, наш код надзвичайно чистий і лаконічний і просто працює.

Отримання без урахування регістру:

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'

Налаштування без урахування регістру:

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'

1
Звичайно, це не вдасться, коли хтось це зробитьobj.setProp("GETPROP")
Бергі,

6

Ще одна варіація на вже представлених, яка підштовхує ітерацію до функції Підкреслення / Лодаша findKey:

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

Наприклад:

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

Як це реалізує underscoreбібліотека? Тобто я шукаю спосіб, який не передбачає лінійного пошуку ключів якогось об’єкта
Зак Сміт

2
@ZachSmith Це O (n); дивіться тут .
Rusty Shackleford

3

Ви можете зробити це для того, щоб "нормалізувати" prop

 var normalizedProp = prop.toLowerCase();
 obj[normalizedProp] = val;

1
Дякую. Я бачу, як застосовувати propмалі літери, але що робити, якщо я не можу гарантувати, що фактична назва властивості буде повністю нижчою? Може мені чогось не вистачає?
Matt Cashatt

1
@MatthewPatrickCashatt Ти нічого не пропускаєш: нічого не можна пропустити - адже мовою тут нічого не допоможе :)

3
Причина голосування проти: Це не відповідає на запитання, оскільки передбачається, як відповідь @ RobG, що властивість нижча.
Matt Goodwin

1

Немає потреби в будь-якій ітерації. Оскільки це propможе бути не рядок, його слід спочатку примусити до рядка, де це доречно, оскільки це те, що об’єкти роблять спочатку. Проста функція отримання:

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

Якщо є вимога обмежити доступ до власних властивостей:

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

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

та сетер:

function objSetter(prop, val) {
  obj[String(prop).toLowerCase()] = val;
}

2
Це передбачає, що всі властивості вихідного об’єкта є малими.
Кріс Хейнс

Погодьтеся з @Hainesy, це передбачає випадок, який суперечить тому, на що було запитання.
Метт Гудвін,

@ MattGoodwin - ви, мабуть, пропустили сеттер, який створює властивість з малою назвою.
RobG

@RobG Моя проблема з цією відповіддю полягає в тому, що для об’єкта з невизначеними іменами властивостей ваше рішення не отримуватиме значення, якщо самі властивості спочатку не були створені з використанням ваших методів або властивості мали нижчий регістр. Я вважаю, що запитувач запитував спосіб доступу до властивостей об’єкта, коли випадок згаданих властивостей не відомий заздалегідь.
Метт Гудвін,

@MattGoodwin, перечитайте авторське запитання. Він не означає, що він не має контролю над об'єктом. Питання конкретне щодо випадку, propякий перейшов у функцію objSetter (). Насправді, включення запитувачів функції objSetter (), насамперед, суттєво означає, що автор контролює створення об’єкта. Відповідь, у якій зазначено, що назви властивостей мають бути нормалізовані до нижньої літери та надає допомогу у виконанні, є розумною відповіддю. Якщо автору потрібно більше, ніж це, то автор може пояснити в коментарі або відредагувавши запитання.
bobpaul

1

чому ми робимо це так складно, коли ми просто можемо зробити все це малими літерами:

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

назвати це: your_object.GetName(your_var.toLowerCase());


5
Це передбачає, що ви маєте контроль над створенням об'єкта і що ключі в об'єкті мають нижній регістр
velop

1

Мені здається хорошим кандидатом на проксі з пастками для перетворення рядкових ключів у верхній або нижній регістр та поведінку як звичайний об'єкт. Це працює або з позначеннями: крапками або брекетами

Ось код:

'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"]


1
const getPropertyNoCase = (obj, prop) => obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];

або

const getPropertyNoCase = (obj, prop) => {
    const lowerProp = prop.toLowerCase(obj[Object.keys(obj).find(key => key.toLowerCase() === prop.toLowerCase() )];
}

0

Ось дуже простий код для цього Припускаючи, що дані - це масив подібних об’єктів

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;
}, [])

Вихід буде

data=[{"a":"bc","b":"nn"}]

0

Можливо, вам доведеться лише збігати регістр (як правило, дорого через ітерацію об’єкта), якщо збіг, чутливий до регістру (дешевий і швидкий), не вдається.

Скажімо, у вас є:

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

І ви з будь-якої причини маєте значення_ 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 ;
          } ;
      } 
  } ;

0

Ще один простий спосіб:

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;
}

Використовуйте його так:

var obj = {
  foo:"bar",
  fizz:"buzz"
};
    getVal(obj,"FoO") -> returns "bar"

0

Ось приємна рекурсивна функція, яка дозволяє здійснити обхід об’єкта javascript без урахування регістру:

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]

0

Приклад ES6, опублікований @nilloc, є неправильним і не працює.

Ось робочий приклад:

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)]);

або ще краще, він повинен повернутися невизначеним, якщо ключ не існує:

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)]);

Однією з міркувань є те, що наведений вище приклад поверне останній відповідний ключ в об’єкті, якщо є кілька відповідних ключів.

Ось приклад з кодом, перетвореним у функцію:

/**
  * @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")]);

0

Ця відповідь вимагає 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)})])


Ваш приклад помилковий, ваш синтаксис для зменшення неправильний. Додайте ще один ключ до свого об’єкта, і ваш приклад зламається, наприклад: 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)})]); Це не вдасться, оскільки першим параметром зменшення є загальна сума, а не значення, і значення буде повернуто як нуль для першого ключа та вдруге через вас буде намагатися зробити .match на null, що призведе до помилки. Я розміщу виправлений приклад як відповідь.
Ден Віллетт,

Дякую, що вказали на це, я не впевнений, чому використовував зменшення. Я оновив свою відповідь за допомогою find () та провів кілька тестів.
nilloc

0

Це дуже сумно, що ітерацію не можна пропустити, як здається. Для мене те, що є прийнятним, але може бути не для всіх, - це сформувати об’єкт один раз за допомогою ітерації, а потім використовувати його звичайним способом хеш-карти.

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);
};

Навіть якщо потрібно виконати лише одноразовий пошук, він не повинен бути менш продуктивним, як будь-яке інше рішення ітерації, оскільки після 1 проходу швидкість пошуку постійна. (Я вважаю).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.