@tsmetadata/json-api
provides a standardized set of JSON:API metadata decorators for classes in TypeScript 5.2+.
By appending metadata to specific classes, class fields, and class methods, we enable out of the box support for developer tooling like ORMs, serialization, resource governance, and more. To continue, with the JSON API decorator metadata approach, you implicitly prioritize modularity and reuse.
npm install @tsmetadata/json-api@latest
The @Resource(type: string)
decorator is available and will define a resource's type
(part of identification).
ex.
import { Resource } from '@tsmetadata/json-api';
@Resource('users')
class User {}
The @Id()
decorator can be applied to one class field and denotes what field contains a resource's id
(part of identification).
ex.
import { Id } from '@tsmetadata/json-api';
class Account {
@Id()
accountNumber: string;
}
The applied metadata can be retrieved using the Symbol idSymbol
export.
import { idSymbol } from '@tsmetadata/json-api';
The @Attribute()
decorator can be applied to many class fields and denotes what fields are resource attributes.
ex.
import { Attribute } from '@tsmetadata/json-api';
class Account {
@Attribute()
isPastDue: boolean;
}
The applied metadata can be retrieved using the Symbol attributesSymbol
export.
import { attributesSymbol } from '@tsmetadata/json-api';
The Relationship(foreignKey: string)
decorator can be applied many times to many class fields and denotes what fields are resource relationships.
The foreign key is type-safe to the field type.
ex.
import { Relationship, type JSONAPIResourceLinkage } from '@tsmetadata/json-api';
class Account {
@Relationship('accounts')
primaryDebtor: Customer | JSONAPIResourceLinkage;
@Relationship('accounts')
coDebtors: Customer[] | JSONAPIResourceLinkage;
}
class Customer {
@Relationship('primaryDebtor')
@Relationship('coDebtors')
accounts: Account[] | JSONAPIResourceLinkage;
}
The applied metadata can be retrieved using the Symbol relationshipsSymbol
export.
import { relationshipsSymbol } from '@tsmetadata/json-api';
The Link()
decorator can be applied to many class fields and denotes what fields are resource links.
ex.
import { Link } from '@tsmetadata/json-api';
class Account {
@Link()
self: string;
@Link()
recentTransactions: string;
}
The applied metadata can be retrieved using the Symbol linksSymbol
export.
import { linksSymbol } from '@tsmetadata/json-api';
The Meta()
decorator can be applied to many class fields and denotes what fields are resource metadata.
ex.
import { Meta } from '@tsmetadata/json-api';
class Account {
@Meta()
createdAt: number;
@Meta()
lastUpdated: number;
}
The applied metadata can be retrieved using the Symbol metaSymbol
export.
import { metaSymbol } from '@tsmetadata/json-api';
The serializeResourceObject(classInstance: object)
function will produce a resource object from a decorated class instance.
ex.
import { Resource, Id, Attribute, serializeResourceObject } from '@tsmetadata/json-api';
@Resource('users')
class User {
@Id()
customerId: string;
@Attribute()
active: boolean;
}
const user = new User();
user.customerId = '123';
user.active = false;
serializeResourceObject(user);
/*
{
"type": "users".
"id": "123",
"attributes": {
"active": false
}
}
*/
The serializeRelationshipObject(classInstance: object)
function will produce a (relationship object)[https://jsonapi.org/format/#document-resource-object-relationships] from a decorated class instance.
ex.
import { Resource, Id, Link, serializeRelationshipObject } from '@tsmetadata/json-api';
@Resource('users')
class User {
@Id()
customerId: string;
@Link()
self: string;
}
const user = new User();
user.customerId = '123';
user.self = 'some-link';
serializeRelationshipObject(user);
/*
{
"data": {
"type": "users",
"id": "123"
},
"links": {
"self": "some-link"
}
}
*/
The serializeResourceObject(classInstance: object, keys: string[])
function will produce an array of resource objects from a decorated class instance.
ex.
import { Resource, Id, Link, serializeIncludedResourceObjects, type JSONAPIResourceLinkage } from '@tsmetadata/json-api';
// For the sake of brevity, the `Account` class definition is not included.
@Resource('users')
class User {
@Id()
customerId: string;
@Relationship('primaryDebtor')
@Relationship('coDebtors')
accounts: Account[] | JSONAPIResourceLinkage;
@Relationship('spouse')
spouse: User | JSONAPIResourceLinkage;
}
const user1 = new User();
user1.customerId = '123';
user1.accounts = [someAccount, someOtherAccount];
const user2 = new User();
user2.customerId = '456';
user2.accounts = [someAccount, someOtherAccount];
serializeIncludedResourceObjects(user1, ['accounts', 'spouse']);
The deserializeResourceObject(resourceObject: JSONAPIResourceObject, cls: new (..._: any[]) => any)
function will produce a class instance from a resource object.
ex.
import { Resource, Id, Attribute, serializeResourceObject, deserializeResourceObject } from '@tsmetadata/json-api';
@Resource('users')
class User {
@Id()
customerId: string;
@Attribute()
active: boolean;
}
const user = new User();
user.customerId = '123';
user.active = false;
const serializedUser = serializeResourceObject(user);
/*
{
"type": "users".
"id": "123",
"attributes": {
"active": false
}
}
*/
const deserializedUser = deserializeResourceObject(user, User);
/*
user.customerId === '123'
user.active === false
*/
ex.
import type { JSONAPIAttributesObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIErrorObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPILinkObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPILinksObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIMetaObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIPaginationLinks } from '@tsmetadata/json-api';
ex.
import type { JSONAPIRelationshipObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIRelationshipsObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIResourceIdentifierObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPIResourceLinkage } from '@tsmetadata/json-api';
ex.
import type { JSONAPIResourceObject } from '@tsmetadata/json-api';
ex.
import type { JSONAPITopLevelObject } from '@tsmetadata/json-api';
import { Attribute, Link, Meta, Relationship, Resource, serializeIncludedResourceObjects,
serializeResourceObject, deserializeResourceObject, type JSONAPIResourceLinkage } from '@tsmetadata/json-api';
@Resource('accounts')
export class Account {
@Attribute()
accountNumber: string;
@Attribute()
pastDue: boolean;
@Relationship('accounts')
primaryDebtor: Customer | JSONAPIResourceLinkage;
@Relationship('accounts')
coDebtors: Customer[] | JSONAPIResourceLinkage;
@Link()
self: string;
@Meta()
lastUpdated: number;
}
@Resource('customers')
export class Customer {
@Id()
id: string;
@Attribute()
name: string;
@Relationship('primaryDebtor')
@Relationship('coDebtors')
accounts: Account[] | JSONAPIResourceLinkage;
@Link()
self: string;
}
const account = new Account();
account.accountNumber = '123';
account.pastDue = false;
account.coDebtors = [];
account.self = 'some-url';
account.lastUpdated = Date.now();
const customer = new Customer();
customer.id = '456';
customer.name = 'Bob';
customer.self = 'some-url';
account.primaryDebtor = customer;
customer.accounts = [account];
const serializedCustomer = serializeResourceObject(customer);
// Try logging out the results on your own!
console.log(
serializedCustomer,
serializeRelationshipObject(customer),
serializeIncludedResourceObjects(customer, ['accounts'])
);
// You can deserialize too!
const customerWithResourceLinkages = deserializeResourceObject(serializedCustomer, Customer);
A: You may be able to take advantage of our Symbol.metadata
polyfill found here.