57

I'm trying to setup an object literal in a JavaScript script that has a key with multiple names. referring to the same object value i.e. something like these that I have already tried:

var holidays: {
    "thanksgiving day", "thanksgiving", "t-day": {
        someValue : "foo"
    }
}

var holidays: {
    ["thanksgiving day", "thanksgiving", "t-day"]: {
        someValue : "foo"
    }
}

Is there a way I can accomplish this?

SomeShinyObject
  • 7,581
  • 6
  • 39
  • 59

12 Answers12

23

Another approach is to do some postprocessing

function expand(obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; ++i) {
        var key = keys[i],
            subkeys = key.split(/,\s?/),
            target = obj[key];
        delete obj[key];
        subkeys.forEach(function(key) { obj[key] = target; })
    }
    return obj;
}

var holidays = expand({
    "thanksgiving day, thanksgiving, t-day": {
        someValue : "foo"
    } 
});
Artem Sobolev
  • 5,891
  • 1
  • 22
  • 40
  • This is better than my suggestion. So long as your data doesn't include commas, I would go with this answer. – Scott Sauyet Feb 07 '13 at 05:09
  • Why the mix of a `for` loop and a `forEach`? I would find it cleaner to stick with one style, probably `forEach`. – Scott Sauyet Feb 07 '13 at 05:10
  • @ScottSauyet, you're right. Firstly I used `for` because I knew that its body will be not so small, while when I iterate over subkeys I execute only one statement, so I was too lazy to write `for (var blah...`. But there is no reason to mix both styles, sure. – Artem Sobolev Feb 07 '13 at 07:40
  • This should do it. I had to prototype in Object.keys and forEach though. Using it in a JScript MS Runtime rather than in a browser. This code is really good. Thanks for the help. – SomeShinyObject Feb 11 '13 at 01:19
21

JSON does not offer such a feature, nor do Javascript object literals.

You might be able to make do with something like this:

holidays = {
    thanksgiving: {foo: 'foo'},
    groundhogDay: {foo: 'bar'},
    aliases: {
        'thanksgiving day': 'thanksgiving',
        't-day': 'thanksgiving',
        'Bill Murrays nightmare': 'groundhogDay'
    }
}

and then you can check

holidays[name] || holidays[holidays.aliases[name]]

for your data.

It's not a wonderful solution. But it wouldn't be too difficult to write a little function that created this sort of object out of a representation like:

[
    {
        names: ['thanksgiving', 'thanksgiving day', 't-day'],
        obj: {foo: 'foo'}
    },
    {
        names: ['groundhogDay', 'Bill Murrays nightmare'],
        obj: {foo: 'bar'}
    },
]

if that would be easier to maintain.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
9

Another solution, if you can afford RegExp execution, and ES6 Proxy:

let align = new Proxy({
    
    'start|top|left': -1,
    'middle|center': 0,
    'end|bottom|right': 1,

}, {

    get: function(target, property, receiver) {

        for (let k in target)
            if (new RegExp(k).test(property))
                return target[k]

        return null

    }

})

align.start     // -1
align.top       // -1
align.left      // -1

align.middle    // 0
align.center    // 0

align.end       // 1
align.bottom    // 1
align.right     // 1

See MDN Proxy

2021 EDIT:
Another (cleaner?) solution using reduce & defineProperty :

const myDict = [
    // list of pairs [value, keys],
    // note that a key should appear only once
    [-1, ['start', 'left', 'top']],
    [0, ['center', 'middle']],
    [1, ['end', 'right', 'bottom']],
].reduce((obj, [value, keys]) => {
    for (const key of keys) {
        Object.defineProperty(obj, key, { value })
    }
    return obj
}, {})
Joseph Merdrignac
  • 3,510
  • 2
  • 19
  • 16
  • 1
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/handler/get – Joseph Merdrignac Dec 07 '17 at 14:55
  • Why return `null`, though? Shouldn't that just be `undefined`, as usual? – Gust van de Wal Sep 24 '21 at 22:12
  • Wow, not so old, but i totally forget that piece of code. Returning null is a convenient way to say "hey, i understand your query, but there is nothing for you here". Since a Proxy is used here, and knowing that a Proxy can handle any "get" access, i found relevant to return null. But returning undefined is correct too, of course. – Joseph Merdrignac Sep 29 '21 at 07:35
  • Sorry, but if you ask me, that could only prohibit people from explicitly using `null` on their own terms. – Gust van de Wal Sep 30 '21 at 23:08
3

I guess you could do something like this:

var holidays = {
  'thanksgiving day': {
    foo: 'foo'
  }
};

holidays.thanksgiving = holidays['t-day'] = holidays['thanksgiving day'];

If you see yourself doing this often or you have more values consider this pattern:

'thanksgiving, t-day, thanks, thank, thank u'.split(',').forEach(function(key) {
  holidays[key] = holidays['thanksgiving day'];
});

A better approach would be to process your data beforehand instead of adding duplicates.

elclanrs
  • 92,861
  • 21
  • 134
  • 171
  • 1
    In case anyone is curious about how this works: In JavaScript, objects are stored by references (whereas literal numbers and strings, which can only be copied). Here, by storing a *reference* to an object that contains the value, when you change the value in any way, all the other properties can access the new value using the same reference. – rvighne Aug 18 '16 at 19:09
2

That should work as expected:

function getItem(_key) {
    items = [{
        item: 'a',
        keys: ['xyz','foo']
    },{
        item: 'b',
        keys: ['xwt','bar']
    }];

    _filtered = items.filter(function(item) {
        return item.keys.indexOf(_key) != -1
    }).map(function(item) {
        return item.item;
    });
    return !!_filtered.length ? _filtered[0] : false;
}
shaedrich
  • 5,457
  • 3
  • 26
  • 42
2

With ES6 you could do it like this, but it's not ideal:

const holidays = {
    "single": {
        singleValue: "foo",
    },

    ...([
        "thanksgiving day", "thanksgiving", "t-day",
    ].reduce((a, v) => ({...a, [v]: {
        someValue: "foo",
    }}), {})),

    "other": {
        otherValue: "foo",
    },
};

I still think the cleanest solution is probably:

let holidays = {
    "t-day": {
        someValue: "foo",
    },
};
holidays["thanksgiving"] = holidays["t-day"];
holidays["thanksgiving day"] = holidays["t-day"];
Malvineous
  • 25,144
  • 16
  • 116
  • 151
1

Now this may be overkill for you, but here's a generic function that will create an object with "multiple keys." What it actually does is have one real property with the actual value, and then defines getters and setters to forward operations from the virtual keys to the actual property.

function multiKey(keyGroups) {
    let obj = {};
    let props = {};

    for (let keyGroup of keyGroups) {
        let masterKey = keyGroup[0];
        let prop = {
            configurable: true,
            enumerable: false,

            get() {
                return obj[masterKey];
            },

            set(value) {
                obj[masterKey] = value;
            }
        };

        obj[masterKey] = undefined;
        for (let i = 1; i < keyGroup.length; ++i) {
            if (keyGroup.hasOwnProperty(i)) {
                props[keyGroup[i]] = prop;
            }
        }
    }

    return Object.defineProperties(obj, props);
}

This is less sketchy than you would expect, has basically no performance penalty once the object is created, and behaves nicely with enumeration (for...in loops) and membership testing (in operator). Here's some example usage:

let test = multiKey([
    ['north', 'up'],
    ['south', 'down'],
    ['east', 'left'],
    ['west', 'right']
]);

test.north = 42;
test.down = 123;

test.up; // returns 42
test.south; // returns 123

let count = 0;
for (let key in test) {
    count += 1;
}

count === 4; // true; only unique (un-linked) properties are looped over

Taken from my Gist, which you may fork.

rvighne
  • 20,755
  • 11
  • 51
  • 73
1

Same reponse (ES6 Proxy, RegExp), but in a shorter way (and significantly less legible)

let align = new Proxy({

    'start|top|left': -1,
    'middle|center': 0,
    'end|bottom|right': 1,

}, { get: (t, p) => Object.keys(t).reduce((r, v) => r !== undefined ? r : (new RegExp(v).test(p) ? t[v] : undefined), undefined) })

align.start     // -1
align.top       // -1
align.left      // -1

align.middle    // 0
align.center    // 0

align.end       // 1
align.bottom    // 1
align.right     // 1
Joseph Merdrignac
  • 3,510
  • 2
  • 19
  • 16
0
//create some objects(!) you want to have aliases for..like tags
var {learn,image,programming} = 
 ["learn", "image", "programming"].map(tag=>({toString:()=>tag }));


//create arbitrary many aliases using a Map
var alias = new Map();
alias.set("photo", image);
alias.set("pic", image);
alias.set("learning", learn);
alias.set("coding", programming);

//best put the original tagNames in here too.. 
//pretty easy huh? 

// returns the image object 
alias.get("pic");

// ;)
0

here is a way you can initialize an object with several keys sharing the same value

var holidays = {
    ...["thanksgiving day", "thanksgiving", "t-day"].reduce((acc, key) => ({ ...acc, [key]: 'foo' }), {})
}

although I would personally think it was more clear if it was written out

0

Object.fromEntries produces some fairly readable and concise code:

var holidays = Object.fromEntries(
        ["thanksgiving day", "thanksgiving", "t-day"].map(k => [k, "foo"]));

The spread syntax can be used to include this alongside other key/value pairs:

var holidaysAndMore = {
    "A": "a",
    ...Object.fromEntries(
            ["thanksgiving day", "thanksgiving", "t-day"].map(k => [k, "foo"])),
    "B": "b"
};
M. Justin
  • 14,487
  • 7
  • 91
  • 130
0

You could take an object with different keys pointing to the same object and a Proxy for shaping setting and getting.

The advantage is to use multiple entry pairs with an array of keys and a value and having only a single value for all grouped keys.

const
    etepetete = entries => new Proxy(
        Object.fromEntries(entries.flatMap(([keys, value]) => keys.map((v => k => [k, v])({ value })))),
        {
            get (target, prop, receiver) {
                if (prop in target) return target[prop].value;
                return Reflect.get(...arguments);
            },
            set (target, prop, value, receiver) {
                if (prop in target) {
                    target[prop].value = value;
                    return true;
                }
            }
        }
    ),
    holiday = etepetete([
        [["thanksgiving day", "thanksgiving", "t-day"], "foo"]
    ]);

console.log(holiday['t-day']);
holiday['thanksgiving day'] = 'bar';
console.log(holiday['t-day']);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392