npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@decahedron/entity

v3.0.1

Published

A library to encode and decode JSON into entity classes

Downloads

390

Readme

Decahedron Entity

This package provides a convenient way to decode JSON retrieved from your API or similar, and turning it into a TypeScript class instance.

Each class is self-encoding, which means that it knows how to encode itself. As such, each class should extend the Entity class in order to work, as it deals with the heavy lifting. Alternatively, your class may implement its own fromJson method.

Installation

Install the package from NPM under the name @decahedron/entity:

yarn add @decahedron/entity

Usage

The basic usage is very straightforward: make your class extend Entity, and use the EntityBuilder to hydrate instances of it:

import { Entity, EntityBuilder } from '@decahedron/entity';

class User extends Entity {
    // We instantiate with null to ensure the property exists
    // at the time of hydration.
    public name: string = null;
    public email: string = null;
}

fetch('https://api.service.com/v1/users/1')
    .then(response => response.Body.json())
    .then(jsonData => EntityBuilder.buildOne<User>(User, jsonData));

You can also build an array of entities:

fetch('https://api.service.com/v1/users')
    .then(response => response.Body.json())
    .then(jsonData => EntityBuilder.buildMany<User>(User, jsonData));

Annotating nested entities

If your endpoint returns a nested object, such as:

{
    "name": "Decahedron Technologies Ltd.",
    "email": "[email protected]",
    "address": {
        "street": "20-22 Wenlock Road",
        "city": "London",
        "zip": "N1 7GU",
        "country": "United Kingdom"
    }
}

The JSON decoding process will ignore the nested object (address). This also applies to arrays of objects (but not to arrays of primitives, which are automatically decoded).

There are two ways to solve this. The first one is to simply override the fromJson method (in fact, this is why we expose the method on the Entity, to make it easy to override decoding functionality):

import { Entity, EntityBuilder } from '@decahedron/entity';

class User extends Entity {
    public name: string = null;
    public email: string = null;
    public address: Address = null;
    
    public fromJson(jsonData: any): User {
        super.fromJson(jsonData);
    	
        if (jsonData.hasOwnProperty('address')) {
            this.address = EntityBuilder.buildOne<Address>(Address, jsonData['address']);
        }

        return this;
    }
}

However, this is quite verbose. Instead, an @Type decorator is provided for nested decoding:

class User extends Entity {
    public name: string = null;
    public email: string = null;
    @Type(Address)
    public address: Address = null;
}

If your JSON data comes in with another key, you may specify that manually with:

@Type(Address, 'json_key')

Note that by default, the @Type decorator will assume your JSON comes in snake case. As such,

@Type(Address)
public homeAddress: Address = null;

will assume that the json holds the key home_address. If that is not the case, it should be manually specified as the second argument to @Type.

Note about Object

If your entity has a nested object that is not represented by another entity, you can also use @Type(Object) to annotate that the object should simply be stored as is.

Encoding back to JSON

Entity objects can also be encoded back to a plain JavaScript Object, or as a JSON string. You can call toJson() on any entity to convert it to a plain JS object.

The method defaults to converting your properties to snake case. To prevent this, you can pass false as the first argument to toJson(). The method also accepts a second boolean argument that lets you specify if the output should instead be as a JSON string. toJson(true, true) is identical to JSON.stringify(toJson(true)).

Circular dependency issue

Because Javascript cannot handle circular dependencies, two related entities cannot annotate each other via the ways shown above. Since v2.7.0, Decahedron Entity solves this issue by importing entity classes only when an entity instance is being built. So instead of importing them at top-level (which would not work as expected):

/* Blog.ts */
import { Entity, Type } from '@decahedron/entity';
import Comment from './Comment';

export default class Blog extends Entity {
    // ...

    @Type(Comment)
    public comments: Comment[] = null;
}

/* Comment.ts */
import { Entity, Type } from '@decahedron/entity';
import Blog from './Blog';

export default class Comment extends Entity {
    // ...

    @Type(Blog)
    public blog: Blog = null;
}

You can now annotate them with an anonymous importer function:

/* Blog.ts */
import { Entity, Type } from '@decahedron/entity';

// You still need to import the annotated class to prevent Typescript and your IDE complaining about it.
import Comment from './Comment';

export default class Blog extends Entity {
    // ...

    @Type(() => require('./Comment'))
    public comments: Comment[] = null;
}

/* Comment.ts */
import { Entity, Type } from '@decahedron/entity';
import Blog from './Blog';

export default class Comment extends Entity {
    // ...

    @Type(() => require('./Blog'))
    public blog: Blog = null;
}

If you are in a browser environment and you cannot use require, you can instead use import(). Make sure you call the async functions of Entity and EntityBuilder instead.


/* Blog.ts */
import { Entity, Type } from '@decahedron/entity';

// You still need to import the annotated class to prevent Typescript and your IDE complaining about it.
import Comment from './Comment';

export default class Blog extends Entity {
    // ...

    @Type(() => import('./Comment'))
    public comments: Comment[] = null;
}

/* Comment.ts */
import { Entity, Type } from '@decahedron/entity';
import Blog from './Blog';

export default class Comment extends Entity {
    // ...

    @Type(() => import('./Blog'))
    public blog: Blog = null;
}

import { EntityBuilder } from '@decahedron/entity';
import Blog from './Blog';

/* somewhere else */
EntityBuilder.buildOneAsync(Blog, json);
EntityBuilder.buildManyAsync(Comment, json);
Blog.fromJsonAsync(json)

To-do

  • [ ] Create an IEntity interface that can be implemented

Contributing

Run the build and the tests using the following commands:

$ npm run build
$ npm test