TypeScript: Typning av rekursiv typ
Jag har en fråga som jag genererar på klientsidan baserat på användarinmatning som ser ut så här.
const query = {
"or": [
{
"field": "username",
"operator": "in",
"value": [
"jdoe",
"jsmith"
]
},
{
"and": [
{
"field": "email",
"operator": "matches",
"value": "/^gmail.com/"
},
{
"or": [
{
"field": "last_sign_in",
"operator": "lt",
"value": 1599619454323
},
{
"field": "last_sign_in",
"operator": "gt",
"value": 1489613454395
}
]
}
]
}
]
}
Men i ett försök att migrera detta till en kortfattad TypeScript-representation har jag svårt att få det att fungera precis som jag vill.
Jag har dessa definitioner:
type Operator = 'eq' | 'in' | 'matches' | 'lt' | 'gt';
type Condition = 'and' | 'or' | 'not';
interface SimpleQuery {
field: string;
operator: Operator;
value: any;
}
interface Query {
condition: SimpleQuery[] // here I want `condition` to come from the type Condition
// I have tried these solutions involving [{ x of y }] https://github.com/microsoft/TypeScript/issues/24220
}
Här är de fel jag får från TS-kompilatorn:
A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.
A computed property name must be of type 'string', 'number', 'symbol', or 'any'.
Cannot find name 'key'.
'Condition' only refers to a type, but is being used as a value here.
Jag har försökt detta med
type Query = {
[key in Condition]: SimpleQuery[];
}
Med detta tillvägagångssätt vill TypeScript att jag också lägger till alla saknade villkor.
Svar #1
Jag tror att detta skulle vara den mest exakta typen för att beskriva de objekt du beskrev:
type Operator = 'eq' | 'in' | 'matches' | 'lt' | 'gt';
type UnionKeys<T> = T extends T ? keyof T : never;
type Condition = UnionKeys<OperatorExpression>;
interface FieldCondition {
field: string;
operator: Operator;
value: any;
}
type BinaryExpression<T extends PropertyKey> = {
[P in T] : [FieldCondition | OperatorExpression, FieldCondition | OperatorExpression]
}
type UnaryExpression<T extends PropertyKey> = {
[P in T] : [FieldCondition | OperatorExpression]
}
type OperatorExpression = BinaryExpression<"and"> | BinaryExpression<"or"> | UnaryExpression<"not">
const query: OperatorExpression = {
"or": [
{
"field": "username",
"operator": "in",
"value": [
"jdoe",
"jsmith"
]
},
{
"and": [
{
"field": "email",
"operator": "matches",
"value": "/^gmail.com/"
},
{
"or": [
{
"field": "last_sign_in",
"operator": "lt",
"value": 1599619454323
},
{
"field": "last_sign_in",
"operator": "gt",
"value": 1489613454395
}
]
}
]
}
]
}
Lekplatslänk
Denna version upprätthåller korrekt aritet för logiska operatorer (med hjälp av tuplatyper) och härledde Condition-unionen baserat på operatorunionen istället.
Svar #2
Jag tror att du behöver göra en rekursion här (lekplats):
type Operator = 'eq' | 'in' | 'matches' | 'lt' | 'gt';
type Condition = 'and' | 'or' | 'not';
interface SimpleQuery {
field: string;
operator: Operator;
value: any;
}
type Query = {
[key in Condition]: Array<SimpleQuery | Query>;
}
const query: Query = {
"or": [
{
"field": "username",
"operator": "in",
"value": [
"jdoe",
"jsmith"
]
},
{
"and": [
{
"field": "email",
"operator": "matches",
"value": "/^gmail.com/"
},
{
"or": [
{
"field": "last_sign_in",
"operator": "lt",
"value": 1599619454323
},
{
"field": "last_sign_in",
"operator": "gt",
"value": 1489613454395
}
]
}
]
}
]
}
