2

Context:
I am working on an application, that generates filters for a GraphQL query dynamically. To work with this generated filters, I am trying to create a Typescript type that resembles the allowed structure of these filters.

Issue:
The Typescript type I need, should allow every string as it's key, with type A as its value. Only certain keys (in this case, "AND" and "OR") should have a different type for their value.

Simplified Example:
The following example is simplified (while still complex enough):
The following filter Object should be allowed:

let filter = {
    "fieldA": "2015-10-08",
    "fieldB": "This is the search input string...",
    OR: [
        {"fieldC1": "deleted"},
        { AND: [
            {"fieldD": "null"},
            {"fieldE": "inactive"}
        ]}
    ],
}

After some search () and reading the docs I came up with this type:

type CustomTypeBase = {
  [key: string]: string;
} & {
  AND?: Array<{[key: string]: string} | CustomTypeBase>;
  OR?: Array<{[key: string]: string} | CustomTypeBase>;
};

This does not work, sadly, as Typescript wants EVERY string-key (including "AND" and "OR) to hold a string value.

I tried to fix this problem with:

type CustomTypeWithOmit = Omit<{
  [key: string]: string;
}, 'AND' | 'OR'> & {
  AND?: Array<{[key: string]: string} | CustomTypeWithOmit>;
  OR?: Array<{[key: string]: string} | CustomTypeWithOmit>;
};

But this does not work either, as the Omit utility type does not work on general types like string, but only works on specific types.

Question:
Is there any way to have a typescript type, that allows type A for all string keys and type B only for certain string keys?

TS Playground link:
Code on TS Playground

3
  • Unfortunately there is no such type in TypeScript. See ms/TS#17867 for the feature request, and the linked q/a above for more information and the various available workarounds. Commented Jul 25, 2024 at 13:25
  • @jcalz See hdevs post for a working solution - even if I do not yet understand why a union type works in this case. Commented Jul 25, 2024 at 23:11
  • Ah, interesting. I suppose because the AND and OR properties are both optional, so there's{} is assignable to both types in the union, you don't get excess property warnings like you normally do. But it's still not the right type, because you can assign surprisingly wrong things to it as shown in this playground link. I guess it's a possible workaround for all-optional types, though. Commented Jul 25, 2024 at 23:21

2 Answers 2

1

You were so close to the solution. This seems to work as expected:

type CustomTypeBase = {
  [key: string]: string;
} | {
  AND?: Array<{[key: string]: string} | CustomTypeBase>;
  OR?: Array<{[key: string]: string} | CustomTypeBase>;
};

To answer your second question you could compose types like:

type YourCustomType = {
  AND?: YourCustomType|StrObj[]
  OR?: YourCustomType|StrObj[]
}
type StrObj = { [key: string]: string | YourCustomType }
Sign up to request clarification or add additional context in comments.

1 Comment

The first part of your answer was exactly what I was looking for, even if I still do not understand why a Union type over two object fixes the problem. Until today, I always assumed, that the Union type would allow a variable to fit either type A or type B - not a combination of both. But in this case the combination is possible and this is exactly what I was looking for. Thanks a lot.
0

You can do the following:

type FilterValue = string | Filter;

type Filter = {
  [key: string]: FilterValue | FilterValue[];
} & {
  AND?: Filter[];
  OR?: Filter[];
};

const filter: Filter = {
    "fieldA": "2015-10-08",
    "fieldB": "This is the search input string...",
    OR: [
        {"fieldC1": "deleted"},
        { AND: [
            {"fieldD": "null"},
            {"fieldE": "inactive"}
        ]}
    ],
}

  1. Define FilterValue type as string or Filter
  2. Define Filter as dictionary with string keys with values of FilterValue or FilterValue[] and special case for OR and AND with other type, in your case only Filter[] array

Here is the playground

1 Comment

While this solution definitely creates a valid type, it loosens the constraints of the initial type allows string-keys to also have an Array of FilterValues as their value. My Intention was to prevent exactly that (string keys only with type A, and distinct types only hold type B). Maybe my question was not clear enough at this point.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.