Consider the following type code:
const shapes = {
circle: {
radius: 10
},
square: {
area: 50
}
}
type ShapeType = typeof shapes
type ShapeName = keyof ShapeType
type ParsedShape<NAME extends ShapeName, PROPS extends ShapeType[NAME]> = {
name: NAME,
properties: PROPS
}
Now, the goal is to utilize the key
of the shapes
object as the shape name during serialization. However, upon deserialization, it should be possible to determine which shape was being referenced. The deserialization code looks like this:
const parseShape = (json: string): ParsedShape<ShapeName, ShapeType[ShapeName]> => {
const parsed = JSON.parse(json)
return {
name: parsed.name,
properties: parsed.properties
}
}
The issue arises when attempting to discriminate between properties of the shape using the name
:
const parsed = parseShape('{"name": "square", "properties": {"area": 50}}')
if (parsed.name === 'square') {
//ERROR
//Property area does not exist on type { radius: number; } | { area: number; }
//Property area does not exist on type { radius: number; }
console.log(parsed.properties.area)
}
It seems that TypeScript fails to recognize that the check is for the shape name and thus doesn't narrow down the properties accordingly.
Is there a viable solution to achieve this requirement, or is it unattainable?
An interim solution currently implemented involves the following workaround, which I would prefer to avoid if feasible:
type ParsedShape<NAME extends ShapeName> = {
[shapeName in NAME]?: ShapeType[shapeName]
}
const parseShape = (json: string): ParsedShape<ShapeName> => {
const parsed = JSON.parse(json)
return {
[parsed.name]: parsed.properties
}
}
const parsed = parseShape('{"name": "square", "properties": {"area": 50}}')
if (parsed.square) {
console.log(parsed.square.area)
}