Home > Net >  How to declare types for an array of objects whose properties are interdependent?
How to declare types for an array of objects whose properties are interdependent?

Time:09-27

I would like to declare type for object (root), that contains nested array of objects (values), where each object has properties one (any type) and all (array of one's type).

Bellow is my attempt to do it, however I don't know how to obtain types of oneproperty, so there is just Value<any>:

type Value<T> = { one: T, all: T[] }
type Root = { values: Value<any>[] }

const root: Root = {
    values: [
        { one: 1, all: 5 }, // Should be error, 'all' should be an array of numbers.
        { one: true, all: [5] }, // Should be error, 'all' should be array of booleans.
        { one: 'a', all: ['a', 'b', 'c'] }, // Ok.
        { one: true, all: [false, true, true] } // Ok.
    ]
}

Is there any way how to do that? Here is example. It would be amazing, if it could be without naming all possible combinations of types like:

type Value = { one: string, all: string[] } | { one: number, all: number[] } | { one: boolean, all: boolean[] }

because it should be applicable to any type.

CodePudding user response:

In Your case T is any because it can be a string, number or boolean. So I would not recommend it.
You can just use something like that:

type Value = number | string | boolean
type ValueObject = { one: Value; all: Value[] }
type Root = { values: ValueObject[] }

CodePudding user response:

The short answer is no; I don't believe there's a way to do what you're trying to do. The longer answer is you probably shouldn't do it.

You could probably store the type information like this:

const root: Root = {
    values: [
        { one: 1, all: 5 } as Value<number>, // Should be error, 'all' should be an array of numbers.
        { one: true, all: [5] } as Value<boolean>, // Should be error, 'all' should be array of booleans.
        { one: 'a', all: ['a', 'b', 'c'] } as Value<string>, // Ok.
        { one: true, all: [false, true, true] } as Value<boolean> // Ok.
    ]
}

...but, I'm struggling to think of a downstream consumption where you'll loop over root.values and not need conditionals for processing the struct like this:

const process = (root) => {
  switch(typeof root) {
    case "number":
      ...
      break
    case "string":
      ...
      break
    case "boolean":
      ...
      break
  }

}

Instead, you could simplify the whole flow by replacing the root with something like:

const intRoots: Value<number>[]
const boolRoots: Value<boolean>[]
const strRoots: Value<string>[]

then process each type explicitly without type checking: const ***Roots.map(...)

A rationalization I fallback on quite frequently (though there are certainly exceptions) is if I'm struggling to manage types in my processing then it's worth revisiting the data structure

  • Related