@tsmetadata/json-api
v2.0.6
Published
Standardized set of JSON:API metadata decorators.
Downloads
474
Maintainers
Readme
JSON API Metadata Decorators
@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.
🌱 Install
npm install @tsmetadata/json-api@latest
📋 Feature Set
⚙️ Usage
Metadata Decorators
Resource / Type
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 {}
Id
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';
Attribute
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';
Relationship
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';
Link
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';
Meta
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';
Serializers
Resource Object
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
}
}
*/
Relationship Object
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"
}
}
*/
Included Resource Objects
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']);
Deserializers
Resource Object
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
*/
Types
Attributes Object
ex.
import type { JSONAPIAttributesObject } from '@tsmetadata/json-api';
Error Object
ex.
import type { JSONAPIErrorObject } from '@tsmetadata/json-api';
JSON API Object
ex.
import type { JSONAPIObject } from '@tsmetadata/json-api';
Link Object
ex.
import type { JSONAPILinkObject } from '@tsmetadata/json-api';
Links Object
ex.
import type { JSONAPILinksObject } from '@tsmetadata/json-api';
Meta Object
ex.
import type { JSONAPIMetaObject } from '@tsmetadata/json-api';
Pagination Links
ex.
import type { JSONAPIPaginationLinks } from '@tsmetadata/json-api';
Relationship Object
ex.
import type { JSONAPIRelationshipObject } from '@tsmetadata/json-api';
Relationships Object
ex.
import type { JSONAPIRelationshipsObject } from '@tsmetadata/json-api';
Resource Identifier Object
ex.
import type { JSONAPIResourceIdentifierObject } from '@tsmetadata/json-api';
Resource Linkage
ex.
import type { JSONAPIResourceLinkage } from '@tsmetadata/json-api';
Resource Object
ex.
import type { JSONAPIResourceObject } from '@tsmetadata/json-api';
Top Level Object
ex.
import type { JSONAPITopLevelObject } from '@tsmetadata/json-api';
😍 Full Example
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);
❓ FAQ
Q: I'm using a legacy runtime that doesn't yet support Symbol metadata.
A: You may be able to take advantage of our Symbol.metadata
polyfill found here.