'Assign a specific string value to first Array position
I have a complex type JoinConditionOptions (it is a SQL conditional object), the idea is to have a never ending nest of arrays, objects and strings. Those are current types and interfaces:
type JoinConditionOptions =
| ConditionOptionsArray
| OnConditionObject
| string
| undefined;
type ConditionOptionsArray = Array<JoinConditionOptions>;
type sqlValues = string | Date | Array<sqlValues> | null | boolean | number;
interface OnConditionObject {
__or?: boolean;
__col_relation?: ColumnRelationObject;
[k: string]:
| sqlValues
| OperatorOptionsObject
| ColumnRelationObject
| undefined;
[y: number]: never;
}
interface OperatorOptionsObject {
like?: string;
notlike?: string;
rlike?: string;
notrlike?: string;
between?: Array<String | number | Date>;
notbetween?: Array<String | number | Date>;
in?: Array<String | number | Date>;
notin?: Array<String | number | Date>;
'>'?: String | number | Date;
'<'?: String | number | Date;
'>='?: String | number | Date;
'<='?: String | number | Date;
'<>'?: String | number | Date;
'!='?: String | number | Date;
'='?: String | number | Date;
}
interface ColumnRelationObject {
[k: string]: string;
}
With that it is possible to do a complex nesting of Arrays, objects and strings with no problems. Example:
[
{
id: [1, 2, 3],
},
'user.id is not null',
{
__or: true,
name: { like: 'john' },
year: 2022,
},
],
That will output:
"(`u`.`id` IN (1,2,3) AND (user.id is not null) AND (`u`.`name` LIKE 'john' OR `u`.`year` = 2022)"
Now the problem is I want to tell typescript that on the first position of any array it can be a string: "__or". I want typescript to show that it is an option when trying to build that JoinConditionOptions object.
I tried to do add "__or" to the type ConditionOptionsArray with an interface merge:
interface FirstOr {
[0]: '__or';
}
type ConditionOptionsArray = Array<JoinConditionOptions> & FirstOr;
But unsuccessfull (causing errors when compiling the example above: "Type 'string' is not assignable to type 'never'")
Any ideas on how to achieve this and maintaining the current features of the JoinConditionsType? I am sorry if I did not make it clear enough, let me know.
Edit 1
Awsering @sno2: TypeScript Playground Link That playground contains my old code. Here the "__or" string recomendation on first positions worked perfectly fine, but then I needed to add another key option to ConditionObject: "__col_relation", this new key is included on that TypeScript Playground, but I just cant make the feature of showing a string "__or" as an option on the first position of ConditionsOptionsArray and at the same time include the new key "__col_relation" in the ConditionObject. Any Ideas on how to combine both of those type structures from those two Ts Playgrounds?
Solution 1:[1]
You can use the tuple literal type notation to have a union of "__or" along with JoinConditionOptions as the first item then have a rest item to type the rest of the items as your JoinConditionOptions type. Here is the solution:
type JoinConditionOptions =
| ConditionOptionsArray
| OnConditionObject
| string
| undefined;
type sqlValues = string | Date | Array<sqlValues> | null | boolean | number;
interface OnConditionObject {
__or?: boolean;
__col_relation?: ColumnRelationObject;
[k: string]:
| sqlValues
| OperatorOptionsObject
| ColumnRelationObject
| undefined;
[y: number]: never;
}
interface OperatorOptionsObject {
like?: string;
notlike?: string;
rlike?: string;
notrlike?: string;
between?: Array<String | number | Date>;
notbetween?: Array<String | number | Date>;
in?: Array<String | number | Date>;
notin?: Array<String | number | Date>;
'>'?: String | number | Date;
'<'?: String | number | Date;
'>='?: String | number | Date;
'<='?: String | number | Date;
'<>'?: String | number | Date;
'!='?: String | number | Date;
'='?: String | number | Date;
}
interface ColumnRelationObject {
[k: string]: string;
}
type ConditionOptionsArray = ["__or" | JoinConditionOptions, ...JoinConditionOptions[]];
const foo: ConditionOptionsArray = ["__or", { like: "foo" }];
const foo2: ConditionOptionsArray = [{ like: "foo" }];
Solution 2:[2]
I managed to achieve this with the code bellow. Note that the type "String" instead of "string" was necessary to typescript recommend "__or" as an option on the first position of the Array.
export type ConditionOptions =
| ConditionOptionsArray
| ConditionObject
| String
| undefined;
interface ConditionOptionsArray extends Array<any> {
[0]?: '__or' | ConditionOptions;
[index: number]: ConditionOptions;
}
type sqlValues = String | Date | Array<sqlValues> | null | boolean | number;
interface ConditionObject {
__or?: boolean;
__col_relation?: ColumnRelationObject;
[k: string]:
| sqlValues
| OperatorOptionsObject
| ColumnRelationObject
| undefined;
[y: number]: never;
}
interface ColumnRelationObject {
[k: string]: String;
}
export const isColumnRelationObject = (
value: any
): value is ColumnRelationObject =>
value !== undefined &&
value !== null &&
!Array.isArray(value) &&
typeof value === 'object' &&
Object.entries(value).reduce(
(acc: boolean, [key, val]) =>
acc && typeof key === 'string' && typeof val === 'string',
true
);
export interface OperatorOptionsObject {
like?: String;
notlike?: String;
rlike?: String;
notrlike?: String;
between?: Array<String | number | Date>;
notbetween?: Array<String | number | Date>;
in?: Array<String | number | Date>;
notin?: Array<String | number | Date>;
'>'?: String | number | Date;
'<'?: String | number | Date;
'>='?: String | number | Date;
'<='?: String | number | Date;
'<>'?: String | number | Date;
'!='?: String | number | Date;
'='?: String | number | Date;
}
const OperatorOptionsObjectKeys: Array<String> = [
'like',
'notlike',
'rlike',
'notrlike',
'between',
'notbetween',
'in',
'notin',
'>',
'<',
'>=',
'<=',
'<>',
'!=',
'=',
];
export const isOperatorOptionsObject = (
val: any
): val is OperatorOptionsObject => {
return (
typeof val === 'object' &&
val !== null &&
val !== undefined &&
Object.keys(val).reduce(
(prev: boolean, cur: String) =>
prev || OperatorOptionsObjectKeys.includes(cur),
false
)
);
};
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | sno2 |
| Solution 2 | Gabriel Cunha |
