Home > Software design >  Typescript - Generic Object type that always ends with string or number
Typescript - Generic Object type that always ends with string or number

Time:10-06

Is there a way in typescript to make a generic object that can be many levels deep but still to always end in either a string or a number property value at the end.

interface GenericObject {
  [key: string]: string | number | GenericObject | GenericObject[];
}

const object: GenericObject = {
  test: 'test',
  testNum: 2,
  test2: {
    test: 'test'
  },
  test3: [
    {
      test: 'test'
    }
  ]
}

Something like this works fine when creating the object, but when trying to access the properties it throws an error.

object.test2.test;
object.test3[0].test;

Property 'test' does not exist on type 'string | number | GenericObject | GenericObject[]'

Is there to make this work so you can an infinite amount of levels deep but to always end on either a string or a number?

Playground url - https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgOIRNYCDyAjAKwgTGQG8BYAKGWQG0BrCATwC5kBnMKUAcwF12XHiF7IAPshABXALZ5oEtBiy5CxUpPSYeaoiTr8A3NQC 1aggD2ILsivqS7bavz7SAXnLVakLuwByPzAAgBofZGCAOTl2ACZwmkiILjj2SiTfFLBA4ICI00SsrgBmdjoI2gzaGuT-ZCDs-MzkcyT MwsqB3cAOmC4-uyTbscwIdK6AAZ CbAjIA

CodePudding user response:

I am not a TS expert (yet). But, I think there is nothing wrong with your definition.

The problem is that in TS you can only access members that are guaranteed to be in all the constituents of a union type.

So it makes sense that you are getting this type of error. There is no sufficient overlap between the types number, string and some GenericObject interface. TS doesn't know if the test2 variable is a primitive or an object with some keys.

One way to tell TS what test2 or test3 props are (at place of access), would be to remap the key via as:

(object.test2 as GenericObject).test;

and leave the object: GenericObject as it is. Just my two cents. I'd be happy to see what other people suggest as a solution to your question (infinitely deep object).

CodePudding user response:

If you want to have both: restriction and property inference, you need just to use extra function.

interface GenericObject {
  [key: string]: GenericObject | GenericObject[] | string | number
}

const validation = <Obj extends GenericObject>(obj: Obj) => obj

const result = validation({
  test: 'test',
  testNum: 2,
  test2: {
    test: 'test'
  },
  test3: [
    {
      test: [{ a: 's' }]
    }
  ]
})

result.test2.test // ok


const withError = validation({
  test: 'test',
  testNum: 2,
  test2: {
    test: 'test'
  },
  test3: [
    {
      test: [{ a: false }] // <--- expected error
    }
  ]
})

Playground

  • Related