12

We use as const to narrow the types:

[ {foo: 'foo' }, { bar: 'bar' } ] as const

but this also adds readonly modifiers for all props and values. As of const this makes sens, but I would like to have the narrowing, without the readonly modifiers, as offen you want to modify the objects in tuple, or just pass it further to some method, which is of the same interface, but without deep readonly

let foo = [ {foo: 'foo' }, { bar: 'bar' } ]

here the foo is of type:

({
    foo: string;
    bar?: undefined;
} | {
    bar: string;
    foo?: undefined;
})[]

but I would expect to get

[{
    foo: string;
}, {
    bar: string;
}]

There for we use as const, but in most cases it is not possible, as foo/bar get deeply nested readonly modifiers, we could use something like

type DeepWritable<T> = { -readonly [P in keyof T]: DeepWritable<T[P]> };

But it would be wired to use as const which adds readonly, and then DeepWritable to remove it.

Do you know any alternative to as const to narrow the tuple type without having manually to define types.

tenbits
  • 7,568
  • 5
  • 34
  • 53
  • How about using [tuples](https://www.typescriptlang.org/docs/handbook/basic-types.html#tuple) then? It doesn't seem that the only common thing about those arrays you show is that all their elements are one-property objects; and that looks like tuple indeed. – raina77ow Oct 10 '20 at 14:02
  • Those tuples are of predefined types - you do this manually, but I would expect types to be infered from literal declarations - `as const` does exactly this, but additionaly adds `readonly` modifiers which makes it useless in most cases without further casting with some Type Utils. – tenbits Oct 10 '20 at 14:09
  • 1
    Are those [answers](https://stackoverflow.com/questions/49729550/implicitly-create-a-tuple-in-typescript) - in particular, `tuple` utility - applicable in your case? I mean, something similar to `let foo = tuple({foo: 'foo' }, { bar: 'bar' })` – raina77ow Oct 10 '20 at 14:14
  • What I actually don't like about this option is the function itself: it _is_ there, in the compiled code, sitting and doing nothing. I suppose that's why the corresponding ticket - https://github.com/microsoft/TypeScript/issues/16656 - is still alive and kicking. – raina77ow Oct 10 '20 at 14:20
  • `tuple` utility is interesting workaround, but as you've mentioned, that stub method in compiled code is not quite good. – tenbits Oct 10 '20 at 14:33
  • The granularity of `const` type assertions can certainly be problematic but, as its primary purpose is brevity, I think it's a good tradeoff. It sounds like you want a sort of tupleness assertion, which could indeed be useful. – Aluan Haddad Oct 10 '20 at 15:14

2 Answers2

6

You can use the following generic function to constrain the type as a tuple:

const Tuple = <T extends [any, ...any]>(v:T) => v

And then wrap the literal:

let foo = Tuple([ {foo: 'foo' }, { bar: 'bar' } ]) // foo: [{ foo: string }, { bar: string }]
ccarton
  • 3,556
  • 16
  • 17
  • Though I just noticed that you were discussing similar answers in the comments. I'll leave this here anyway for you to consider. – ccarton Oct 10 '20 at 14:45
  • Thanks, right, but anyway - this workaround is also quite interesting. – tenbits Oct 10 '20 at 14:47
  • 1
    Cool, `` seems to provide good inference (sometimes I use `T extends ReadonlyArray` but it's more limited). Do you know any documentation about it? – tokland Jan 18 '21 at 22:57
0

Here is a better generic Tuple function, since it preserves tuple member literal values too:

export type Writable<T> = {
  -readonly [K in keyof T]: T[K];
};
export const Tuple = <T extends readonly [unknown, ...unknown[]]>(v: T) =>
  v as Writable<T>;

Use it like so:

const foo = Tuple([{foo: 'foo' }, { bar: 'bar' }] as const)
// foo: [{ readonly foo: 'foo' }, { readonly bar: 'bar' }]
// Note the array itself is not readonly, only the object properties are

See the relevant TypeScript 3.4 release note for reference.

Also worth checking out this related, more thorough answer

Geoff Davids
  • 887
  • 12
  • 15