I'm trying to create a type to represent a list (hash) of HTTP headers. So this type should be a hash containing only key / string pairs:
type TStringStringHash = {
[key: string]: string
}
But this allows an empty object of type TStringStringHash
to be instantiated:
const foo: TStringStringHash = {}
Which doesn't really make sense in my implementation. I want an object of type TStringStringHash
to hold at least one indexed property:
const foo: TStringStringHash = { foo: "bar" }
Every answer I've found so far addresses how to force either one of two non-indexed optional properties to be assigned, no answer I could find seems to address this particular issue.
I apologize in advance if the answer to this happens to be trivial, but so far I haven't been able to figure this out on my own.
Thanks!
CodePudding user response:
Build the hash using a function, where you can constrain the argument according to your needs:
type StringEntry = [key: string, value: string];
type HashEntries = [StringEntry, ...readonly StringEntry[]];
function createHash (entries: HashEntries): Record<string, string> {
return Object.fromEntries(entries);
}
const hash1 = createHash([
['name', 'value'],
]);
console.log(hash1); // { name: "value" }
const hash2 = createHash([
['name', 'value'],
['name2', 'value2'],
]);
console.log(hash2); // { name: "value", name2: "value2" }
createHash(); /*
~~~~~~~~~~~~
Expected 1 arguments, but got 0.(2554) */
createHash([]); /*
~~
Argument of type '[]' is not assignable to parameter of type '[StringEntry, ...StringEntry[]]'.
Source has 0 element(s) but target requires 1.(2345) */
createHash([
['name'], /*
~~~~~~~~
Type '[string]' is not assignable to type 'StringEntry'.
Source has 1 element(s) but target requires 2.(2322) */
]);
createHash([
['name', 'value', 'extra'], /*
~~~~~~~~~~~~~~~~~~~~~~~~~~
Type '[string, string, string]' is not assignable to type 'StringEntry'.
Source has 3 element(s) but target allows only 2.(2322) */
]);
CodePudding user response:
It is possible to validate empty objects:
type OnlyLiteral<Hash extends Record<string, string>> =
Record<string, string> extends Hash ? never : Hash
type NotEmpty<Hash extends Record<string, string>> =
keyof Hash extends never ? never : Hash
const hash = <
Hash extends Record<string, string>
>(obj: OnlyLiteral<NotEmpty<Hash>>) => {
}
const empty = {}
const emptyTyped: Record<string, string> = {}
hash({}) // expected error
hash(empty) // expected error
hash(emptyTyped) //expected error
hash({ a: 'a' }) // ok
You just need to negate all disallowed values and to use never
instead of them.
Above solution should be thoroughly tested. Also it works only with literal arguments
If you are interested in this approach you can check my article