I am using the Map dictionary in TypeScript and I want to have the get and has properties to work case insensitive. How can I make this work ?
let envVariables = new Map<string, string>();
envVariables.set('OS', 'Windows_NT');
envVariables.set('USERNAME', 'SYSTEM');
if (this.envVariables.has('UserName')) {
// this should work with case insensitive search
}
In C# the Dictionary constructor just needs the StringComparer.OrdinalIgnoreCase and then the dictionary will be case insensitive.
Map
doesn't support this behaviour. It can be extended in order for keys to be stored and looked up in case-insensitive fashion. Since Map
internally uses set
on construction, the constructor doesn't need to be augmented.
It's straightforward with TypeScript es6
or higher target because ES6 built-in classes support extends
:
class CaseInsensitiveMap<T, U> extends Map<T, U> {
set(key: T, value: U): this {
if (typeof key === 'string') {
key = key.toLowerCase() as any as T;
}
return super.set(key, value);
}
get(key: T): U | undefined {
if (typeof key === 'string') {
key = key.toLowerCase() as any as T;
}
return super.get(key);
}
has(key: T): boolean {
if (typeof key === 'string') {
key = key.toLowerCase() as any as T;
}
return super.has(key);
}
}
Native classes should be treated in a special way when being extended in TypeScript with es5
target:
interface CaseInsensitiveMap<T, U> extends Map<T, U> {}
class CaseInsensitiveMap<T, U> {
constructor(entries?: Array<[T, U]> | Iterable<[T, U]>) {
return Reflect.construct(Map, arguments, CaseInsensitiveMap);
}
set (key: T, value: U): this {
if (typeof key === 'string') {
key = key.toLowerCase() as any as T;
}
return Map.prototype.set.call(this, key, value) as this;
}
get (key: T): U | undefined {
if (typeof key === 'string') {
key = key.toLowerCase() as any as T;
}
return Map.prototype.get.call(this, key) as U;
}
has(key: T): boolean {
if (typeof key === 'string') {
key = key.toLowerCase() as any as T;
}
return Map.prototype.has.call(this, key) as boolean;
}
}
This is my variant of a "true" Case Insensitive Map created using TypeScript: it contains a private map that stores original keys.
export class CaseInsensitiveMap<TKey, TVal> extends Map<TKey, TVal> {
private keysMap = new Map<TKey, TKey>();
constructor(iterable?: Iterable<[TKey, TVal]>){
super();
if (iterable) {
for (const [key, value] of iterable) {
this.set(key, value);
}
}
}
set(key: TKey, value: TVal): this {
const keyLowerCase = typeof key === 'string'
? key.toLowerCase() as any as TKey
: key;
this.keysMap.set(keyLowerCase, key);
return super.set(keyLowerCase, value);
}
get(key: TKey): TVal | undefined {
return typeof key === 'string'
? super.get(key.toLowerCase() as any as TKey)
: super.get(key);
}
has(key: TKey): boolean {
return typeof key === 'string'
? super.has(key.toLowerCase() as any as TKey)
: super.has(key);
}
delete(key: TKey): boolean {
const keyLowerCase = typeof key === 'string'
? key.toLowerCase() as any as TKey
: key;
this.keysMap.delete(keyLowerCase);
return super.delete(keyLowerCase);
}
clear(): void {
this.keysMap.clear();
super.clear();
}
keys(): IterableIterator<TKey> {
return this.keysMap.values();
}
*entries(): IterableIterator<[TKey, TVal]> {
const keys = this.keysMap.values();
const values = super.values();
for (let i = 0; i < super.size; i++) {
yield [keys.next().value, values.next().value];
}
}
forEach(callbackfn: (value: TVal, key: TKey, map: Map<TKey, TVal>) => void): void {
const keys = this.keysMap.values();
const values = super.values();
for (let i = 0; i < super.size; i++) {
callbackfn(values.next().value, keys.next().value, this);
}
}
[Symbol.iterator](): IterableIterator<[TKey, TVal]> {
return this.entries();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With