PhiceDev
PhiceDev

Reputation: 527

Typescript , what's the best approach for declaring a nested object with different types?

I have this JS object and I want to port it to Typescript:

var items = [{
      style: 'activity-8',
      color: '#a32c62',
      id:    8,
      done : false,
      label : {
        short :'999 m',
        long  :'walk 999m',
        statstime  :'yesterday'
      },
      val : {
        today : {
          target   : { 
            raw : 0,
            display :"11"
          },
          achieved : {
            raw : 0,
            display :"22"
          }
        },
        yesterday : {
          target   : {
            raw : 0,
            display :"33"
          },
          achieved : {
            raw : 0,
            display :"44"
          }
        }
      }
    },{
      style: 'activity-7',
      color: '#ec575d',
      id:    7,
      done : true,
      label : {
        short :'walk 555m',
        long  :'walk 555m',
        statstime  :'yesterday'
      },
      val : {
        today : {
          target   : { 
            raw : 0,
            display :"0"
          },
          achieved : {
            raw : 0,
            display :"0"
          }
        },
        yesterday : {
          target   : {
            raw : 0,
            display :"0"
          },
          achieved : {
            raw : 0,
            display :"0"
          }
        }
      }
    }];

what's the best approach to declare this object type? shall I write down the types for every field? Or create a custom type? any other suggestions?

Upvotes: 4

Views: 18908

Answers (2)

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92547

If you don't want to extract subtypes (nested fields as separate definitions) because they are used only in that one place, then you can define Interface as follows (code from head)

interface MyItem {
  style: string,
    color: string,
    id: number,
    done: boolean,
    label: {
      short: string,
      long: string,
      statstime: string,
    },
    val: {
      today: {
        target: {
          raw: number,
          display: string
        },
        achieved: {
          raw: number,
          display: string,
        }
      },
      yesterday: {
        target: {
          raw: number,
          display: string,
        },
        achieved: {
          raw: number,
          display: string,
        }
      }
    }
};


// and map your not-typed items table (eg readed from API) to typed
let typedItem: MyItem[] = items.map(item => new MyItem(item));

And use is like this

let items: MyItem[] = [{
  style: 'activity-8',
  color: '#a32c62',
  id: 8,
  done: false,
  label: {
    short: '999 m',
    long: 'walk 999m',
    statstime: 'yesterday'
  },
  val: {
    today: {
      target: {
        raw: 0,
        display: "11"
      },
      achieved: {
        raw: 0,
        display: "22"
      }
    },
    yesterday: {
      target: {
        raw: 0,
        display: "33"
      },
      achieved: {
        raw: 0,
        display: "44"
      }
    }
  }
}, {
  style: 'activity-7',
  color: '#ec575d',
  id: 7,
  done: true,
  label: {
    short: 'walk 555m',
    long: 'walk 555m',
    statstime: 'yesterday'
  },
  val: {
    today: {
      target: {
        raw: 0,
        display: "0"
      },
      achieved: {
        raw: 0,
        display: "0"
      }
    },
    yesterday: {
      target: {
        raw: 0,
        display: "0"
      },
      achieved: {
        raw: 0,
        display: "0"
      }
    }
  }
}];

Here is working example

Upvotes: 2

chnging
chnging

Reputation: 775

I don't know if it's the "best" way but I will give you example of my way of doing this.

I will try to explain my general rule not only for nested objects:

For all the properties that are base type (string, number, boolean or some Array of something) you can leave them like this, but for every other complex property/nested object that from now on I will call 'Complex property' (because it makes more sense to me) you make an Interface that will be the type of the property.

Example: In your case the val property is a 'complex' property so let's split it, starting bottom to top.

The smallest complex property in the val property is target, so you make an interface called Target (or ITarget it's not exactly a convention to do that in ts) Target will be something like:

interface Target {
  raw: number,
  display: string
}

You do the same thing for the achieved 'complex' property.

Now you can go one level up. Today property is also a 'complex' one so it has to have a type that probably is going to be some type of interface. Thanks to our previous work that interface will look like:

interface Day {
  target: Target,
  achieved: Achieved
}

You are probably wondering why the interface is called Day and not Today, well the reason is that you have to find out what is the type of the yesterday 'complex' property. As you can see you can see it is the same type as the today property because it has the same properties inside.

So finally the val property that was our goal will have it's own interface that will be something like:

interface Val {
  today: Day,
  yesterday: Day
}

Next you do the same thing for every other 'complex' property.

Now you can use those in your main object which probably will be a class and its properties' types will be those interfaces.

Don't forget that the interface in ts(and not only) is just a 'helping' tool that only helps to organize your code design. Every interface most of the time should has to have a class that implements that interface. And those are the classes that you will use to actually set some values to those properties.

UPDATE (4 years later):

Now when I have more experience with typescript I will still use the same strategy but I'd use the type and not interface to do the pretty much the same job. The interfaces (for me) should be used only if you intend to have a class that would implement them.

Upvotes: 11

Related Questions