typescript - Converting a wider type to a more narrow type - Stack Overflow

admin2025-04-18  0

I believe I am trying to convert a wider type to a more narrow type, which is the opposite to the Liskov substitution principle(?)

How can I tell the TS compiler that the narrowed properties "a" and "b" exist on the property without using type assertions?

type Original = Record<string, [string, ...string[]]>;
type Converted = Record<"a" | "b", string>;

let orig: Original = { a: ["1"], b: ["2"] };
let conv: Converted = Object.fromEntries(
  Object.entries(orig).map(([key, value]) => [key, value[0]]),
);
Type '{ [k: string]: string; }' is missing the following properties from type 'Record<"a" | "b", string>': a, b ts(2739)

I think the satisfies keyword might be needed here, but I cannot work out how to apply it to the code above.

I believe I am trying to convert a wider type to a more narrow type, which is the opposite to the Liskov substitution principle(?)

How can I tell the TS compiler that the narrowed properties "a" and "b" exist on the property without using type assertions?

type Original = Record<string, [string, ...string[]]>;
type Converted = Record<"a" | "b", string>;

let orig: Original = { a: ["1"], b: ["2"] };
let conv: Converted = Object.fromEntries(
  Object.entries(orig).map(([key, value]) => [key, value[0]]),
);
Type '{ [k: string]: string; }' is missing the following properties from type 'Record<"a" | "b", string>': a, b ts(2739)

I think the satisfies keyword might be needed here, but I cannot work out how to apply it to the code above.

Share edited Mar 7 at 12:16 myol asked Mar 7 at 11:46 myolmyol 10k24 gold badges97 silver badges158 bronze badges 3
  • 2 I mean, your conv variable doesn't match the Converted type though does it? You map over the entries to create an array of objects, not a new singular object --- e.g. the value you're putting into conv is [{a: "1"}, {b: "2"}], whereas the type would imply a value of {a: "1", b: "2"} which it's not getting – toastrackengima Commented Mar 7 at 11:58
  • The question has been updated to have working code - the error is unchanged – myol Commented Mar 7 at 12:17
  • 1 You annotated orig as Original so TS can't possibly know about a and b. So your desired narrowing is indeed the opposite of the LSP, which means it's inherently unsafe (you could change a to z and TS couldn't tell that this happened). If you want TS to let you do an unsafe thing, then the most straightforward and reasonable thing to do is use a type assertion. Please edit to clarify why you want/expect to do this without type assertions, because right now this sounds like "I'd like to open a can without using a can opener" and leads to more questions. – jcalz Commented Mar 7 at 13:04
Add a comment  | 

1 Answer 1

Reset to default 0

It's not easy problem even to approach (how to make it right, what way?). So

  1. Standard JS function are often typed more loosely than you need, so prepare to make your own utilities
  2. In implementing those we need probably casting anyway, I think that casting in utilities is totally OK since low-level and narrow scoped.
  3. Start with as const satisfies to preserve exact literal types to the end.

so a solution (no implementation though) could look like that (seems could be improved though, every improvement seems like a separate question, so the problem is rather big).

You could try to override the standard Object.entries, etc for example, though I'm not sure how that'd affect existing code.

Playground

type Original = Record<string, [string, ...string[]]>;
type Converted = Record<"a" | "b", string>;

let orig = { a: ["1"], b: ["2"] } as const satisfies Original;

type FromEntries<T extends readonly [string, any]> = {
    [K in T[0]]: Extract<T, readonly [K, any]>[1];
};

type Simplify<T> = T extends Function ? T : {[K in keyof T]: Simplify<T[K]>} extends infer A ? A : never;

declare function entries<T extends Record<string, any>>(obj: T): {[I in keyof T]:[I, T[I]] }[keyof T][];
declare function fromEntries<const T extends (readonly [string, any])[]>(obj: T): Simplify<FromEntries<T[number]>>;
declare function map<T extends any, R>(item: T[], cb: (item: T) => R): R[]



const e = entries(orig);
const s = map(e, (item) => {
    const [k, v] = item; 
    return k ==='a' ? [k,v[0]] as const:[k,v[0]] as const;
});

let conv = fromEntries(s) satisfies Converted;
转载请注明原文地址:http://conceptsofalgorithm.com/Algorithm/1744932099a275218.html

最新回复(0)