Making a SuperStruct EthereumAddress type
SuperStruct is a great library for validating data in Javascript. It provides proper TypeScript types, which has made me favor it to some of the competitors like Joi.
I want it to work well and be as strict as possible for Ethereum addresses (0x...).
Let’s say I’m receiving this token
object from some kind of user input and I want to validate it.
const token = {
address: "0x728713b41fcEc5C071359Ecf2802Fa0B62bec5a8"
}
With superstruct, we can make a simple schema and validate this.
import { is, object, string } from 'superstruct'
const TokenSchema = object({
address: string(),
})
is(token, TokenSchema) // true
But string
isn’t very specific in this regard. This shouldn’t be possible:
const invalidToken = {
address: "asdfg"
}
is(invalidToken, TokenSchema) // true ❌ SHOULD BE FALSE
Let’s fix this!
Making a custom EthereumAddress SuperStruct type
Let’s make a custom type in SuperStruct.
import { define } from "superstruct";
const EthereumAddress = define("EthereumAddress", (value) => {
// validation logic here
});
How do we validate an Ethereum address? One easy way is by using the Ethers library.
import { utils } from "ethers";
utils.isAddress("0x728713b41fcEc5C071359Ecf2802Fa0B62bec5a8") // true
utils.isAddress("asdf") // false
Let’s put this into our new type.
// Yes, I am using isString from lodash. Too lazy.
import { isString } from "lodash";
const EthereumAddress = define("EthereumAddress", (value) => {
// first check if it is a string
if(!isString(value)){
return false
}
// then check if it is a valid address
return utils.isAddress(value);
});
Now we can supercharghe our TokenSchema
with our new type.
const TokenSchema = object({
address: EthereumAddress
});
const token = {
address: "0x728713b41fcEc5C071359Ecf2802Fa0B62bec5a8"
}
is(token, TokenSchema) // true
const invalidToken = {
address: "asdfg"
}
is(invalidToken, TokenSchema) // false
Give the right types to typescript
One of my favourite things with SuperStruct is that you can infer types to use in Typescript. But our new EthereumAddress
type gives us an unknown
😲.
type Token = Infer<typeof TokenSchema>
// type Token = {
// address: unknown
//}
We can fix this by explicitly telling SuperStruct that the closest TypeScript type is string
when defining the type.
We do this by using the generic define<string>
.
const EthereumAddress = define<string>("EthereumAddress", (value) => {
...
})
Now it gives us the proper TypeScript type.
type Token = Infer<typeof TokenSchema>
// type Token = {
// address: string
//}
If you want it to be even prettier, you could add an actual EthereumAddress
TypeScript type.
type EthereumAddress = string
const EthereumAddress = define<EthereumAddress>("EthereumAddress", (value) => {
...
})
type Token = Infer<typeof TokenSchema>
// type Token = {
// address: EthereumAddress
//}
Conclusion
SuperStruct is great by itself, but can be even greater by adding some custom types.
Check out the CodeSandbox.