In Typescript the following seems like it should accomplish the creation of the desired type:
interface RecordX extends Record<string, string[]> {
id: string
}
but this complains about:
Property 'id' of type 'string' is not assignable to string index type 'string[]'. ts(2411)
How can one add a property of a different type to a Record<>
utility type?
Generally, how can one describe an object with heterogenous-value-type fixed properties but homogenous-value-type properties that are dynamically added.
S, for example given this object:
const a = {
// some properties with known "hard-coded" types
id: '123',
count: 123,
set: new Set<number>(),
// and some dynamic properties
dynamicItemList: ['X', 'Y']
anotherDynamicallyAddedList: ['Y', 'Z']
} as ExtensibleRecord
So how can one define a type or interface ExtensibleRecord
where:
id
, count
, and set
are fixed as string
, number
and Set<number>
dynamicItemList
and anotherDynamicallyAddedList
and any other properties added to the object are string[]
I've tried many variants that I'd think might work, including:
type ExtensibleRecord = {
id: string, count: number, set: Set<number>
} & Record<string, string[]>
type ExtensibleRecord = {
id: string, count: number, set: Set<Number>
} & Omit<Record<string, string[]>, 'id'|'count'|'set'>
interface ExtensibleRecord = {
id: string,
count: number,
set: Set<number>,
[k: string]: string[]
}
but each seems to result in errors.
This feels like something common and obvious, but I can't find an example or reference.
Record<string, string []> means that the id property, if it exists, must be a string []. You can't extend it with something that doesn't match that; you're trying to make an exception, not an extension, and TypeScript does not (yet) support that as a concrete type.
There's no generic constraint that requires a type to be a record. Records satisfy either the classor structconstraint. To make a constraint on a specific hierarchy of record types, put the constraint on the base record as you would a base class.
A record type doesn't have to declare any positional properties. You can declare a record without any positional properties, and you can declare other fields and properties, as in the following example: public record Person(string FirstName, string LastName) { public string[] PhoneNumbers { get; init; } = Array.Empty<string>(); };
Beginning with C# 9, you use the recordkeyword to define a reference typethat provides built-in functionality for encapsulating data. You can create record types with immutable properties by using positional parameters or standard property syntax: public record Person(string FirstName, string LastName);
If you are not using it for class
, you can describe it using type
:
type RecordWithID = Record<string, string[]> & {
id: string
}
let x: RecordWithID = {} as any
x.id = 'abc'
x.abc = ['some-string']
http://www.typescriptlang.org/play/#code/C4TwDgpgBAShDGB7ATgEwOoEtgAsCSAIlALywIqoA8AzsMpgHYDmANFLfcwNoC6AfFABkUAN4BYAFBQomVAC52dRk0kBfSZIA2EYFAAeCuEjRZchEqNVQAhtRsMQGiXoB0siwHJrAI3genrj7wFlxe1KgAZh48TkA
From the official docs, it's not possible to achieve what you want:
While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name’s type does not match the string index’s type, and the type checker gives an error:
interface NumberDictionary { [index: string]: number; length: number; // ok, length is a number name: string; // error, the type of 'name' is not a subtype of the indexer }
However, according to this site, excluding certain properties from the index signature is possible in one situation: when you want to model a declared variable. I'm copying the entire section from the site.
Sometimes you need to combine properties into the index signature. This is not advised, and you should use the Nested index signature pattern mentioned above. However, if you are modeling existing JavaScript you can get around it with an intersection type. The following shows an example of the error you will encounter without using an intersection:
type FieldState = { value: string } type FormState = { isValid: boolean // Error: Does not conform to the index signature [fieldName: string]: FieldState }
Here is the workaround using an intersection type:
type FieldState = { value: string } type FormState = { isValid: boolean } & { [fieldName: string]: FieldState }
Note that even though you can declare it to model existing JavaScript, you cannot create such an object using TypeScript:
type FieldState = { value: string } type FormState = { isValid: boolean } & { [fieldName: string]: FieldState } // Use it for some JavaScript object you are gettting from somewhere declare const foo:FormState; const isValidBool = foo.isValid; const somethingFieldState = foo['something']; // Using it to create a TypeScript object will not work const bar: FormState = { // Error `isValid` not assignable to `FieldState isValid: false }
Finally, as a workaround, if it fits, you might create a nested interface (check the section "Design Pattern: Nested index signature" of site, in which the dynamic fields are in a property of the interface. For instance
interface RecordX {
id: string,
count: number,
set: Set<number>,
dynamicFields: {
[k: string]: string[]
}
}
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