1

I am working on a library where a list of rules get's defined. I want to turn this list of rules into an interface for something to abide by the rules.

I end up getting a list that is similar to this in structure:

const rules = [
    {} as { foo: 1 | 2 | 3},
    {} as { bar: 4 | 5 | 6},
]
type T = typeof rules

The type of T ends up being union array like so:

type T = ({
    foo: 1 | 2 | 3;
} | {
    bar: 4 | 5 | 6;
})[]

But what I actually want is an interface that merges them:

type T = {
  foo?: 1 | 2 | 3
  bar?: 4 | 5 | 6
}

Is there any way to achieve this?

JD Isaacks
  • 56,088
  • 93
  • 276
  • 422
  • You can explicitly define the type and tell the array that it is your type: `const rules: MyType[] = []`. Or am I missing the point? It works perfectly well with your merged example type. – Silvermind Aug 03 '21 at 13:39
  • @Silvermind I am trying to determine the type from a set of rules. Explicitly defining it, defeats the whole purpose. The rules have to be created. I would like a block of rules to be able to also define an interface that can be used to satisfy them. I want to avoid having to create a set of rules that basically define an interface and then also define the matching interface and have to keep them in sync. The rules in this example are simplified, but a rule might be like `{backgroundColor: "black" | "white"}` and another rule `{padding: 10 | 20}`. – JD Isaacks Aug 03 '21 at 13:46
  • If you are holding CSS rules in the rules variable, then I would say to you that to use `rules: CSS.Properties[]`. Sorry if I didn't understand it. – Subrato Pattanaik Aug 03 '21 at 13:53
  • 1
    The rules variable is a lot more complex than that. It's describes (creates) a property that can be set on a React component, it also describes what values are ok for this property, it also specifies a parser to turn those values into appropriate css properties. For example a color parser that can look up a special color name from a theme. It also describes how to turn all this into a single css rule that can be interpolated into a styled component. None of this info is actually necessary to solve my typescript problem tho, thus I left it out. I just need a way to get an interface from the set – JD Isaacks Aug 03 '21 at 14:00
  • Do we have an interface or type alias for each object in the rules array? – Subrato Pattanaik Aug 03 '21 at 14:26
  • 1
    @SubratoPatnaik yeah a rule gets defined, and returns a type like Rule where K is a type that is a string literal property name. And V is a list of acceptable values. For example Rule and I get an array of these. I want to turn this array into an interface that combines them like { backgoundColor: themeLight | themeDark | ..., ...otherRules } – JD Isaacks Aug 03 '21 at 14:30

1 Answers1

5

It seems like you are trying to:

  1. Turn a union into an intersection.
  2. Infer the types of items in an array.
  3. Make all properties optional.

If we break them down this way, we can solve each problem one at a time.

To turn a union into an intersection, you can check this magic:

type UnionToIntersection<U> = 
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

To infer the types of items in an array, you can create another utility type:

type InferArray<T extends any[]> = T extends (infer U)[] ? U : never;

To make all properties optional, TS already have a built-in utility for that:

type OptionalAll<T> = Partial<T>;

We can now combine them:

const rules = [
  {} as { foo: 1 | 2 | 3 },
  {} as { bar: 4 | 5 | 6 },
];

type UnionToIntersection<U> = 
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type InferArray<T extends any[]> = T extends (infer U)[] ? U : never;

type T = Partial<UnionToIntersection<InferArray<typeof rules>>>;

Check the playground here.

yqlim
  • 6,898
  • 3
  • 19
  • 43