I am given a large complicated interface X that I cannot modify, I would like to define some smaller types based on the properties and sub-properties within the interface X that I want to use as types for parameters of a function. I would also like to avoid redeclaring separately what is already inside X.
interface X {
a:number;
c:{
aa:number;
cc:{
aaa:string;
}
},
d?:{
aa:number;
cc:{
aaa:string;
}
},
e:{
aa:number;
cc:{
aaa:string;
}
}[]
}
I understand that I can use mapped types to access the sub-type [Edit note: sub-type is the wrong terminology, this should be called Nested-level types, see answer below] this should be of the object, for example:
type C = X["c"]; // { aa:number; cc:{ aaa:string } }
type C_CC = X["c"]["cc"]; // { aaa: string }
type D = X["d"]; // { aa:number; cc:{ aaa:string } } | undefined
However, things got a bit hairy when the property is possibly undefined. If I want to get at the type at X.d.cc...(which should give {aaa:string} ) how do I do it? Or is it even possible?
The following two attempts gives an error:
type D_CC1 = X["d"]["cc"]; // error? Due to the (d?) possibly undefined?
type D_CC2 = Required<X["d"]>["cc"]; // error also?
Separately, I am also a bit confused when arrays are involved, for example above for E:
type E = X["e"]; // { aa:number; cc:{ aaa:string } }[]
type E_CC1 = X["e"]["cc"]; // error
type E_CC2 = X["e"][0]["cc"]; // got the result I wanted: { aaa: string }
The third line seems to work, but I am not sure what is the logic of using [0] -- seems hacky. I would like to know if this is the right way.
Edit: Changed type names from D_CC to D_CC1 and D_CC2 to avoid ambiguity.
Just a detail but in order to not mix up terminology, what you have in your interface are not subtypes, but nested-level types. Nested-level types have top-level types. Subtypes are types that you can use wherever a type that has this subtype is required.
Next, what you are calling mapped types are actually what we call the keying-in operator. That's a way to look up property types in a shape.
Mapped types are used to map over an object's key and value types and it looks like this:
type MyMappedType = {
[Key in K]: ValueType
}
That's also where you could leverage the keyof operator. Key in keyof Something
For example, to make all fields in a shape optional (there's a built-in mapped types called Partial<Object>
for this), you could use mapped types
:
type ShapeAllOptional = {
[K in keyof MyShape]?: MyShape[K]
}
Now on to your question.
type D_CC1 = X["d"]["cc"]; // error? Due to the (d?) possibly undefined?
You could use something like this
type myStype = NonNullable<X['d']>['cc']
As to using
type E_CC1 = X["e"]["cc"]; // error
type E_CC2 = X["e"][0]["cc"]; // got the result I wanted: { aaa: string }
Yes, it's an array with each item having that shape. I don't know if it's hacky but I did see something once that looks a bit "cleaner".
type E_CC2 = X["e"][number]["cc"];
There's no better way I'm aware of.
Ah, I almost forgot.
type D_CC2 = Required<X["d"]>["cc"]; // error also?
Careful, what you're saying here is make all properties in X['d'] required, that is aa
and cc
, which are... already required. Required<X>
would I think achieve what you want.
If you take a look at your interface, you'll notice that e
property is actually defined as array.
e:{
aa:number;
cc:{
aaa:string;
}
}[] // NOTICE THIS!
That's the reason you need to use indexer [0]
to get the type of item of that array. It's not a hack, actually it's added in typescript since 2.1 and the official name is indexed access types, also called lookup types.
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