7 code-style variations for strongly-typed JSON

Destructure raw JSON whilst automatically adding type information

3 minute read

Intro

I don’t know about you, but I like my JSON data to get typed as soon as possible.

Whether I wrap it, assign it or destructure it, it’s great to be able to see properties pop up as you type.

Whilst exploring ways to do this, I’ve been consistently surprised how flexible TypeScript’s compiler can be – especially in regard to destructuring – so a while ago I started noting the various ways to do it.

Dataset

Let’s say you have some contact data with top-level and nested properties:

const data = {
  "id": 1,
  "name": "joe",
  "contact": {
    "email": "joe@bloggs.com",
    "phone": "020 0000 0000"
  }
}

For the sake of momentum let’s assume this data is safe; either it’s hardcoded JSON or has been sanitised.

It would be good to get it fully typed, so:

  • TypeScript will complain if we try to do something we shouldn’t
  • we can dot-into both top level and nested data
  • we can pass it around the app

Let’s begin by defining the types:

type User = {
  id: number,
  name: string,
  contact: Contact
}

type Contact = {
  email: string,
  phone: string
}

The rest of the article will demonstrate variations in code-style to safely map our types to the data.

Code

The naive way would be to assign types manually:

function process (data: any) {
  const id: number = data.id
  const name: string = data.name
  const contact: any = data.contact
}

But we can do better than that, as we’ve already have some type information:

function process (data: any) {
  const id: number = data.id
  const name: string = data.name
  const contact: Contact = data.contact
}

If destructuring is your thing, you can do that too:

function process (data: any) {
  const { id, name, contact }: { id: number, name: string, contact: Contact } = data
}

But we’re duplicating effort, as we could leverage our existing User type:

function process (data: any) {
  const { id, name, contact }: User = data
}

Moving the type to the parameter makes for one less step:

function process (user: User) {
  const { id, name, contact } = user
}

We can even destructure within the parameter!

function process ({ id, name, contact }: User) {
  // ...
}

If there’s some reason you can’t type the parameter, you can as to assert (opens new window) before you destructure:

function process (data: any) {
  const { id, name, contact } = data as User
}

If you’re passing to a function that expects a User type, you can assert inline within the call:

process(data as User)

Conclusion

ES6 destructuring and TypeScript types make a great combination, and TypeScript’s compiler is flexible enough that there’s multiple ways to type values with a terse and expressive style.

I hope you learned something!

So...