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?
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?
Share Improve this question asked Oct 12, 2016 at 7:35 PhiceDevPhiceDev 5272 gold badges6 silver badges23 bronze badges 1-
interface per type
should be good enough – harishr Commented Oct 12, 2016 at 7:51
2 Answers
Reset to default 11I 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 plex 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 'plex' property so let's split it, starting bottom to top.
The smallest plex 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
'plex' property.
Now you can go one level up. Today
property is also a 'plex' 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
'plex' 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 'plex' 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.
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