I'm trying to come up with an interface that will behave similarly to any
in that properties can be accessed with the dot notation object.foo.bar
and will describe "recursive object of primitives" that
- can only have keys of type
string
- can only have values of type
Primitive | Primitive[]
Where Primitive can be defined as type Primitive = string | boolean | number
and by "nested object" I mean that the value can also be another recursive object of primitives.
So far I've come up with the following:
type Primitive = string | number | boolean
interface IPrimitveObject extends Record<string, IPrimitveObject | IPrimitveObject[] | Primitive | Primitive[]> {}
But this fails right on the second level of property access.
const testObject: IPrimitveObject = {
foo: 'bar',
bar: {
baz: true
}
}
testObject.bar.baz //compiler error: "Property 'bar' does not exist on type 'string | number | boolean | IPrimitveObject | IPrimitveObject[] | Primitive[]'."
Which makes sense. But how can one overcome this?
Once you annotate a variable as type
IPrimitiveObject
, that's it's type, and the compiler won't keep track of any particular properties on it that are not known in the definition ofIPrimitiveObject
.If you want to create an object literal and have the compiler check that it is assignable to
IPrimitiveObject
without actually widening its type toIPrimitiveObject
and subsequently forgetting all of its specific properties, you can make a generic helper function which only accepts arguments assignable toIPrimitiveObject
, and returns them unchanged:And you'd use it instead of annotating:
And if you use something incompatible you should get an error:
Okay, hope that helps; good luck!
Playground link to code