最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

TypeScript how to cast strongly typed ReadonlyMap (to exclude undefined in Map.get(key) return type)? - Stack Overflow

programmeradmin1浏览0评论

Trying use Map for implementing dictionary

however despite typing all posible keys and using ReadonlyMap Map.get(key) returns valueType | underfined

type dictKeys = 'key1' | 'key2' | 'key3'

const dictObj:ReadonlyMap<DictKeys, string> = new Map<DictKeys, string>{
    key1: 'value1',
    key2: 'value2',
    key3: 'value3',
} satisfies Record<dictKeys, string>

function testDictObj(key: dictKeys) {
    const res = dictObj[key]
    return res
}

const dictMap:ReadonlyMap<DictKeys, string> = new Map([
    ['key1', 'value1'],
    ['key2', 'value2'],
    ['key3', 'value3'],
])

function testDictMap(key: dictKeys) {
    const res = dictMap.get(key)
    return res
}

IMHO TS should know all needed tyes for narrowing and if there invalid key's type or there is missing prop key definition in Map - it gives a warning so const res = dictMap.get(key) should give a 'string' type only

If using Object - all is fine but for dict it's more semantic to use Maps Yes I can use assertion "!" cos I am shure for result but it seems less neat (

Maybe there is already walkaroung or custom utility type fo it?

Edit to add The final solution to problem (to get Dictionary as ReadonlyMap instead as object and have strictly typed get() method which given proper key type return values withpout underfine) found as:

function getEntries<
   T extends Record<PropertyKey, unknown>,
   K extends keyof T,
   V extends T[K]
>(o: T) { return Object.entries(o) as [K, V][] }

interface T_Dict<K extends PropertyKey, V> extends ReadonlyMap<K, V> {get(key: K): V}

export class DictObject<K extends PropertyKey, V> extends Map<K, V> implements T_Dict<K, V>{
private constructor(obj: Record<K, V>) {
    super(getEntries(obj))
}
public static create<K extends PropertyKey, V>(obj: Record<K, V>) {
    return new DictObject(obj) as T_Dict<K, V>
}

public override get(key: K): V {
    if (!this.has(key)) throw new Error('Wrong key!')
    return super.get(key) as V
}

}

Trying use Map for implementing dictionary

however despite typing all posible keys and using ReadonlyMap Map.get(key) returns valueType | underfined

type dictKeys = 'key1' | 'key2' | 'key3'

const dictObj:ReadonlyMap<DictKeys, string> = new Map<DictKeys, string>{
    key1: 'value1',
    key2: 'value2',
    key3: 'value3',
} satisfies Record<dictKeys, string>

function testDictObj(key: dictKeys) {
    const res = dictObj[key]
    return res
}

const dictMap:ReadonlyMap<DictKeys, string> = new Map([
    ['key1', 'value1'],
    ['key2', 'value2'],
    ['key3', 'value3'],
])

function testDictMap(key: dictKeys) {
    const res = dictMap.get(key)
    return res
}

IMHO TS should know all needed tyes for narrowing and if there invalid key's type or there is missing prop key definition in Map - it gives a warning so const res = dictMap.get(key) should give a 'string' type only

If using Object - all is fine but for dict it's more semantic to use Maps Yes I can use assertion "!" cos I am shure for result but it seems less neat (

Maybe there is already walkaroung or custom utility type fo it?

Edit to add The final solution to problem (to get Dictionary as ReadonlyMap instead as object and have strictly typed get() method which given proper key type return values withpout underfine) found as:

function getEntries<
   T extends Record<PropertyKey, unknown>,
   K extends keyof T,
   V extends T[K]
>(o: T) { return Object.entries(o) as [K, V][] }

interface T_Dict<K extends PropertyKey, V> extends ReadonlyMap<K, V> {get(key: K): V}

export class DictObject<K extends PropertyKey, V> extends Map<K, V> implements T_Dict<K, V>{
private constructor(obj: Record<K, V>) {
    super(getEntries(obj))
}
public static create<K extends PropertyKey, V>(obj: Record<K, V>) {
    return new DictObject(obj) as T_Dict<K, V>
}

public override get(key: K): V {
    if (!this.has(key)) throw new Error('Wrong key!')
    return super.get(key) as V
}

}

Share Improve this question edited Jan 20 at 14:07 Poul asked Jan 18 at 16:45 PoulPoul 3693 silver badges13 bronze badges 9
  • 1 (note: I have not downvoted this question) You can certainly write a type that works this way, but you have not demonstrated a valid use case for it. Your dictMap is just of type Map<DictKeys, string> since you used satisfies. Anyone can call delete() on it. If you annotate dictMap as ReadonlyMap<DictKeys, string> then TS doesn't know the keys are all present. Both problems as shown in this playground link. Please edit to show a valid use case, or this question risks being closed as needing details or clarity. – jcalz Commented Jan 18 at 20:07
  • Thank for reply I am not so good in TS so it still surprises me) For instatnce satisfies ReadonlyMap in my code was insufitient and provied const dictMap with all props of just map I should annotate dictMap with type ReadonlyMap (const dictMap:ReadonlyMap<DictKeys, string> = ...) and this gives dictMap of type ReadonlyMap which has not method .delete – Poul Commented Jan 19 at 21:50
  • 1 And in your new version, nothing requires that all the keys are present, as shown in this playground link. Thus if you want to narrow to a map that is known to have all possible keys, you need to either assert or write an implementation which convinces TypeScript of it. The answer you accepted does the latter. – jcalz Commented Jan 19 at 22:27
  • Yep, you are right iven if I wrap /include disered type of map keys everywhere in Map creation TS does not force dictMap to use it /Changed original question so it's more clear a question not pretending that code is fully correct – Poul Commented Jan 20 at 9:05
  • 1 The first two articles are, with respect, incorrect and misleading at best. The third one is pretty solid. But even that one is for Javascript, and this is Typescript. If you care about specific keys known at compile-time, use an object because they're treated as special by the compiler. They're also faster in most cases. And serialize directly to/from JSON. Don't get me wrong, I use Map fairly often (and Set even more) but the use case you've presented isn't one of those times. – Jared Smith Commented Jan 20 at 21:46
 |  Show 4 more comments

1 Answer 1

Reset to default 2

While TypeScript can do magic stuff with type inference on the object type, it has to follow the defined type definitions of the Map class. And both Map and ReadonlyMap define the get method like this:

get(key: K): V | undefined;

So no way that TypeScript can know automatically that the value can't be undefined.

You could extend the Map class like this:

class StrictMap<K, V> extends Map<K, V> {
    public override get(key: K): V {
        const result = super.get(key);
        if (result === undefined) {
            throw Error("Key not found");
        }
        return result;
    }
}

By using this implementation the get method never returns undefined, it throws an exception instead.

And if you need to cast it to Readonly you can extend this interface as well:

interface ReadonlyStrictMap<K, V> extends ReadonlyMap<K, V> {
    get(key: K): V;
}

Now your example works and get returns string:

const dictMap = new StrictMap([
    ['key1', 'value1'],
    ['key2', 'value2'],
    ['key3', 'value3'],
]) satisfies ReadonlyStrictMap<dictKeys, string>;

function testDictMap(key: dictKeys) {
    const res = dictMap.get(key)
    return res // <-- Type: string
}

(EDIT: The satisfies in the example is not really needed, just kept it to be close to your own example.)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论