Is it bad practice to map an object to its own property?

Viewed 173

I often find myself passing around objects that have IDs or names that identify them but are also a property of the object. For example:

type ExampleItemName = string;
interface ExampleItem {
  name: ExampleItemName ;
  value: number;
}

I also often need to create lists of these items, and I find it's convenient to be able to access the items by their names. The most logical data structure I see for this is a Map, ie:

type ExampleItemList = Map<ExampleItemName, ExampleItem>;

This is handy and convenient, as I can access any item by its name with Map.get but I can also easily iterate through the whole list using for...of.

However, this feels wrong to me because the name information is duplicated - it is present both in the key and the value of the map. If the name of an ExampleItem changes, the corresponding key in the map doesn't, so it feels fragile.

Is this a bad practice? If so, what's a better way to achieve the functionality I am looking for?

2 Answers

This isn't bad practice per se, but there are a few conditions that need to be met or you run into problems like the problem you mentioned with the value you key it by changing. This pattern is usually referred to as the act of keying your data. It is best practice to key your data by values that you know will not change, like an ID. Since dictionaries can often reduce the cost of expensive find functions they are necessary in many situations, but like I mentioned and you pointed out, you must either key them by a value that will not change and is unique or you will need to update and handle duplicates every time you use the dictionary. Which solution is needed depends on your situation.

In addition, libraries like lodash have built in functions (which you can easily write yourself) that take in data and key them for you. You will also find these kinds ideas used extensively in other languages even if they go by different names (python: dictionary, Java: hashMap, etc.)

If it's not terribly inconvenient, you can try to create your own list structure that satisfy your use case, such as the following one.

class NameMap<K, V extends { name: K }> {
    private items: Set<V> = new Set();
    Add(value: V) { this.items.add(value); }
    Get(key: K) {
        for(let e of this.items.values()) if(e.name == key) return e;
        return undefined;
    }
    [Symbol.iterator]() { return this.items[Symbol.iterator](); };
}

type ExampleItemList = NameMap<ExampleItemName, ExampleItem>;
Related