Write typescript type for given shape
I am trying to write an interface in typescript for the given shape but using recursion, and I also want leaf node to be of type HTMLInputElement only
const form: Form = {
_type: 'object',
number: {
type: 'number'
},
string: {
type: 'text'
},
boolean: {
type: 'checkbox'
},
object: {
_type: 'object',
number: {
type: 'number'
},
string: {
type: 'text'
},
},
numbers: {
type: 'select',
value: '12',
},
strings: {
type: 'select'
},
booleans: {
type: 'select'
},
objects: {
_type: 'array',
number: {
type: 'number'
},
string: {
type: 'text'
},
}
}
I tried this
type Input = (Partial<HTMLInputElement> & { type: string })
type FromObject = ({ _type: 'object' | 'array' } & { [key: string]: FromObject | Input }) | Input
type Form = { _type: 'object' | 'array' } & FromObject
so the idea is a form config can have a key _type
with allowed values object | array
, if _type is not in the object then it should be of type HTMLInputElement only.
error at key object
,
Type '{ _type: "object"; number: { type: string; }; string: { type: string; }; }' is not assignable to type 'FromObject'.
Type '{ _type: "object"; number: { type: string; }; string: { type: string; }; }' is not assignable to type '{ _type: "object" | "array"; } & { [key: string]: FromObject; }'.
Type '{ _type: "object"; number: { type: string; }; string: { type: string; }; }' is not assignable to type '{ [key: string]: FromObject; }'.
Property '_type' is incompatible with index signature.
Type 'string' is not assignable to type 'FromObject'.
also the leaf nodes are not HTMLInputElement
, which I'm trying to avoid.
Your primary issue is that you're trying to represent an object with a shape like "a dictionary where all properties have values of type X
, except for the _type
property, which should have a value of type Y
, where Y
is not assignable to X
." TypeScript does not support such types. If you have an index signature, it means all the properties, even _type
, need to be assignable to its value type. Here's an example:
type Problem = {
_type: 'object' | 'array'; // error!
// Property '_type' of type '"object" | "array"' is
// not assignable to string index type 'Input'
[key: string]: Input;
}
There is an open feature request, microsoft/TypeScript#17687, to support objects like this. You could think of it as a "dictionary with some exceptions", or a "set of known properties with a 'rest' index signature". And it has remained an open feature request for three years as of Aug 2020. For now, there are no obvious plans in the works to address this.
There are workarounds, but none of them are particularly great.
One workaround is to use an intersection as you seem to have done. This allows you to define the problematic type, and even to use objects which are already known to be of that type, but it doesn't allow you to easily create objects of that type. You get the index signature incompatibility error you've shown above. If you want to work around that you can use things like Object.assign()
, but it's so very clunky:
const fm = (t: { _type: 'object' | 'array' }, k: { [k: string]: FromObject | Input }): Form =>
Object.assign(t, k);
const form: Form = fm({
_type: 'object'
}, {
number: { type: 'number' },
string: { type: 'text' },
boolean: { type: 'checkbox' },
object: fm({ _type: 'object' }, {
number: {
type: 'number'
},
string: {
type: 'text'
},
}),
numbers: { type: 'select', value: '12', },
strings: { type: 'select' },
booleans: { type: 'select' },
objects: fm({
_type: 'array'
}, {
number: {
type: 'number'
},
string: {
type: 'text'
},
})
});
Basically you have to force the compiler into performing the intersection explicitly, so it doesn't notice the inconsistency.
The answer to the other question goes on about another workaround involving generics. This is arguably going to be even worse for you because of the nested structure, and I'm not inclined to go through the tedious exercise of coming up with something that will work.
The "right" thing to do, according to TypeScript, would be to refactor your code so as not to need these "mixed" types. Push the dictionary down one level:
type Form2 = ({ _type: 'object' | 'array', props: { [key: string]: FromObject2 | Input } });
type FromObject2 = Form2 | Input
And then make your form2
like this:
const form2: Form2 = {
_type: 'object',
props: {
number: {
type: 'number'
},
string: {
type: 'text'
},
boolean: {
type: 'checkbox'
},
object: {
_type: 'object',
props: {
number: {
type: 'number'
},
string: {
type: 'text'
},
}
},
numbers: {
type: 'select',
value: '12',
},
strings: {
type: 'select'
},
booleans: {
type: 'select'
},
objects: {
_type: 'array',
props: {
number: {
type: 'number'
},
string: {
type: 'text'
}
}
}
}
}
That compiles with no error at all and will be much, much easier to use in TypeScript. If you have an existing JS code base and can't refactor, I understand, in which case there will be a headache no matter what you do.
One final possible workaround is just to loosen the constraint:
type Form3 = ({ _type: 'object' | 'array', [key: string]: FromObject3 | Input | 'object' | 'array' });
type FromObject3 = Form3 | Input
now a Form3
or a FromObject3
will allow "object"
or "array"
for any property. And so _type
satisfies the index signature and gets this to compile:
const form3: Form3 = {
_type: 'object',
number: {
type: 'number'
},
string: {
type: 'text'
},
boolean: {
type: 'checkbox'
},
object: {
_type: 'object',
number: {
type: 'number'
},
string: {
type: 'text'
},
},
numbers: {
type: 'select',
value: '12',
},
strings: {
type: 'select'
},
booleans: {
type: 'select'
},
objects: {
_type: 'array',
number: {
type: 'number'
},
string: {
type: 'text'
},
}
}
but with the looser constraint it also accepts this:
const oops: Form3 = {
_type: 'array',
strings: 'object',
booleans: 'array'
}
so you'd have to be careful.
'개발하자' 카테고리의 다른 글
Terraform - 네트워크 피어링을 위한 Azure Kubernetes AKS vnet ID를 찾는 방법 (0) | 2023.04.24 |
---|---|
Kubernetes : 패치를 사용하여 서비스 포트를 변경하는 방법 (0) | 2023.04.23 |
Float로 고정 바닥글로 스크롤 보기를 만드는 방법은 무엇입니까? (0) | 2023.04.22 |
TypeScript의 인터페이스 대 유형 (0) | 2023.04.22 |
플라우터럭: 카드를 클릭할 수 있게 만드는 방법은 무엇입니까? (0) | 2023.04.21 |