Object typing

Bright Inventions
3 min readJan 6, 2021

--

Introduction

As developers we often perform POST and PATCH requests to API from our frontend apps. When we use TypeScript, this code should be typed safely. There are multiple ways to do so. Let’s analyze one of them!

Object typing

Prerequisite

Let’s imagine a player entity from a game:

// Create a type for our object
type PlayerType = {
hp: number
name: string
position: [number, number]
}

// We can also use interface:
interface PlayerInterface {
hp: number
name: string
position: [number, number]
}

Let’s create two functions (without safe type control as for now) to create and update our player:

Let's create two functions (without safe type control as for now) to create and update our player:const postPlayer = (data: any) => {
// API call with POST method, here as data whole Player object should be sent.
}

const patchPlayer = (data: any) => {
// API call with PATCH method, here as data Player object can be sent as a whole or partialy.
}

Safe typing

Leaving type any can lead application to behave erroneous moreover we do not benefit from TypeScript static type checking. Avoid leaving any in code - there is probably a better solution!

Statically typed functions arguments could look like this:

// Here we use PlayerType (or PlayerInterface) type as API require whole object 
const postPlayer = (data: PlayerType | PlayerInterface) => { }

// Example of "keyof" operator
type PlayerTypeKeys = keyof PlayerType // "hp" | "name" | "position"

// Copy of PlayerType type with "?" beeing added - this will change object properties to be optional
type PlayerTypeOptional = {
[K in keyof PlayerType]?: PlayerType[K]
}

// Here we use PlayerOptional (or partial of PlayerInterface) type as API does not require whole object
const patchPlayer = (data: PlayerTypeOptional | Partial<PlayerInterface>) => { }

Let’s test our type for safety:

// Here we create objects which later will be send to API thre proper functions
const player1: PlayerType = { hp: 10, name: "Albert", position: [10, 1] }
const player2: PlayerInterface = { hp: 20, name: "Antony", position: [2, 20] }
const player3: PlayerTypeOptional = { name: "Alex" }
const player4: Partial<PlayerInterface> = { hp: 2 }

// Results
postPlayer(player1) // OK
postPlayer(player2) // OK
postPlayer(player3) // Error
postPlayer(player4) // Error
patchPlayer(player1) // OK
patchPlayer(player2) // OK
patchPlayer(player3) // OK
patchPlayer(player4) // OK

Additional info

Similarly to adding optional (“?”) modificator on PlayerType we can add by "+" (this sign can be omitted) or remove by "-" (this sign is required) readonly modificator:

type PlayerTypeReadonlyOne = {
readonly [K in keyof PlayerType]: PlayerType[K]
}

type PlayerTypeReadonlyTwo = {
+readonly [K in keyof PlayerTypeOptional]-?: PlayerTypeOptional[K]
}

type PlayerTypeOptionalTwo = {
-readonly [K in keyof PlayerTypeReadonlyOne]+?: PlayerTypeReadonlyOne[K]
}

// types PlayerTypeReadonlyOne and PlayerTypeReadonlyTwo are equivalent
const player5: PlayerTypeReadonlyOne = { hp: 1, name: "One", position: [1, 1] }
const player6: PlayerTypeReadonlyTwo = { hp: 2, name: "Two", position: [2, 2] }

// types PlayerTypeOptional and PlayerTypeOptionalTwo are equivalent
const player7: PlayerTypeOptionalTwo = { position: [2, 2] }

As an alternative while using interface SomeInterface instead of type we can use a combination of Readonly<SomeInterface>, Required<SomeInterface>.

Conclusion

Proper API typing increase benefits from TypeScript static type checking and makes our development safer. Knowledge on how to work with types or interfaces can help us find a way to do so in a DRY way!

Originally published at https://brightinventions.pl on January 6, 2021.

By Wojciech Baczyński, Fullstack developer @ Bright Inventions

--

--

Bright Inventions
Bright Inventions

Written by Bright Inventions

Software Development Studio with expertise in mobile & web applications, Blockchain, AI and IOT device integrations. https://brightinventions.pl

No responses yet