How to automatically map JSON data to a TypeScript object

How to automatically map JSON data to a TypeScript object

Interpreting data doesn't have to be that hard. You can do it in a single line.

Introduction

Interacting with JSON APIs, files, configs, etc. doesn't have to be such a hassle. Keeping your code clean and maintainable can be difficult when using loosely-typed data that can change at any time and, as I've talked about in my previous article about Anti-Pattens, it introduces a lot of hard-coded logic in more than one place.

The situation ๐Ÿค”

Let's say that we need to map some JSON data to our TypeScript class. The incoming data looks like this:

{
    "_param_1": "something",
    "param2": "something else",
    "items": ["item 1", "item 2", "item 3"],
    "something": {
        "inner1": "inner thing",
        "inner2": "another inner thing"
    },
    "thingToBeIgnored": "some useless data"
}

While our class looks like this:

class TestClass {
    public param1: string;
    public param2: string;
    public list: string[];
    public thing: AnotherClass;
    public thingToBeIgnored: string = "Leave me here!"
}

How do we go about mapping one to another?

The bad way ๐Ÿ˜‘

Of course, the most cumbersome (and most used) way of doing it looks something like this:

function mapToClass(json: any) : TestClass {
    const result = new TestClass();
     result.param1 = json["_param_1"];
    result.param2 = json.param2;
    // ...ok I think you get the picture.
}

Doing it in other ways will NOT ensure a true instance of our class.

The ONE-LINE alternative โœจ

For this we will need tapi.js , a tiny, zero-dependency, auto-mapper for TypeScript.

npm i -S tapi.js

Next, be sure to enable decorators in your tsconfig.json file.

"compilerOptions" : {
    "experimentalDecorators": true
}

Now, let's decorate ๐ŸŒน our class and make it buildable. In our case it looks something like the following, however you can read the docs to better understand the process.

import { BuildableResource, Properties } from "tapi.js";

@Properties.Resource
class TestClass extends BuildableResource {
    @Properties.Alias("_param_1")
    public param1: string;

    @Properties.Transform(incomingValue => {
        return incomingValue.toUpperCase();
    })
    public param2: string;

    @Properties.ListOf(string)
    public list: string[];

    public thing: AnotherClass;

    @Properties.Ignore
    public thingToBeIgnored: string = "Leave me here!"
}

Let's convert! ๐Ÿ˜

Now your conversion happens automatically... in both ways and respecting the original formatting of the JSON data!

From JSON to class

let instance = new TestClass().fromJSON(data);

From class to JSON

let data = instance.toJSON();

Mapping from promises ๐Ÿคž

Wanna use it with promises? Simple!

import axios from 'axios' // ๐Ÿ‘ˆ Of course, you can use whatever library you want

import { BuildableResource, ... } from 'tapi.js'

import 'tapi.js/extensions' // ๐Ÿ‘ˆ Use this line to import all the extended functionalities of core types

// Let's create a simple class...
class TestClass extends BuildableResource {
    // You know the drill by now...
}

// Then make a request and get a promise...
axios.get('/some-url-that-returns-an-object')
    // Now let's build the object with its defined builder! ๐ŸŽ‰
    .as(TestClass)
    // Aaaaand we can use the typed object to do whatever we want.
    .then((builtObject) => {
        builtObject.doSomething();
    })

Conclusion

I hope this article and this tool is helpful to you in some way, I'm actually the author of the NPM package and would love four you to try it and give me some feedback.

I've used it in a lot of projects and can assure you that it works... but don't take MY word for it, take YOURS!

Until next time! ๐Ÿ‘‹

ย