Let's say I have this 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, I want to use the key
of the shapes
object as the shape name when serialising. But after deserialisation I want to be able to figure out which shape it was. So I have this deserialisation code:
const parseShape = (json: string): ParsedShape<ShapeName, ShapeType[ShapeName]> => {
const parsed = JSON.parse(json)
return {
name: parsed.name,
properties: parsed.properties
}
}
The problem is - I am not able to discriminate the properties of the shape using 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)
}
So TypeScript is not seeing that I am actually checking for the shape name and is not narrowing down the properties.
Is there a way to achieve what I want or it is not possible?
An ugly workaround I am currently using is this and I would rather avoid that if possible:
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)
}
Okay, so after a few answers I've got nudged into the right direction and found what I wanted. I took the code suggested by @0xts and asked GPT4 if it can be simplified, and that got me closer to the final result.
Basically, the "workaround" I was using was pretty close to the goal (notice the }[NAME]
at the end of the type declaration):
type ParsedShape<NAME extends ShapeName> = {
[shapeName in NAME]: {
name: shapeName
properties: ShapeType[shapeName]
}
}[NAME]
const parseShape = (json: string) => {
return JSON.parse(json) as ParsedShape<ShapeName>
}
const parsed = parseShape('{"name": "square", "properties": {"area": 50}}')
if (parsed.name === 'square') {
console.log(parsed.properties.area)
}
Thanks everyone!