name: yayson description: Serialize and parse JSON API data with yayson. Use when writing Presenters, setting up Stores (standard or legacy), or working with yayson relationships and schema validation.
YAYSON
Setup
// Standard (JSON API 1.0)
import yayson from 'yayson'
const { Presenter, Store } = yayson()
// Legacy (pre-1.0 format)
import yayson from 'yayson/legacy'
const { Presenter, Store } = yayson()
// Sequelize adapter
const { Presenter } = yayson({ adapter: 'sequelize' })
// Utility helpers for reading symbols off store models
import { getType, getMeta, getLinks, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
Presenters
Presenters serialize JS objects into JSON API documents. Subclass Presenter and set static type.
Basic presenter
class UserPresenter extends Presenter {
static type = 'users'
}
UserPresenter.render({ id: 1, name: 'Ada' })
// { data: { type: 'users', id: '1', attributes: { name: 'Ada' } } }
Field filtering
class UserPresenter extends Presenter {
static type = 'users'
static fields = ['name', 'email'] // whitelist, excludes everything else
}
Relationships
Return a map of property name to Presenter class from relationships(). Related data goes into included.
class WheelPresenter extends Presenter {
static type = 'wheels'
}
class BikePresenter extends Presenter {
static type = 'bikes'
relationships() {
return { wheels: WheelPresenter }
}
}
BikePresenter.render({ id: 1, wheels: [{ id: 10 }, { id: 11 }] })
// data.relationships.wheels.data = [{ type: 'wheels', id: '10' }, ...]
// included = [{ type: 'wheels', id: '10', ... }, ...]
For to-many relationships and conditionally loaded (?include=) responses, declare cardinality and/or optional semantics via the config form:
class TicketPresenter extends Presenter {
static type = 'tickets'
relationships() {
return {
addons: { presenter: AddonPresenter, hasMany: true }, // empty → data: []
parentTicket: { presenter: TicketPresenter, optional: true }, // key absent → omitted
guestTickets: { presenter: TicketPresenter, hasMany: true, optional: true },
}
}
}
hasMany: true→ empty/missing data renders asdata: []instead ofdata: null(spec-compliant for to-many).optional: true→ when the relationship key is absent from the instance, the relationship is omitted from output entirely (or rendered as{ links }only iflinks()configures one for that key). An explicitnullon the instance still renders asdata: null—optionaldistinguishes "not loaded" from "explicitly empty".
The bare-class form is unchanged.
Custom attributes
Override attributes() to transform or compute attributes.
class EventPresenter extends Presenter {
static type = 'events'
attributes(instance) {
const attrs = super.attributes(instance)
return { ...attrs, slug: attrs.name.toLowerCase().replace(/ /g, '-') }
}
}
Links
Override selfLinks() for resource links and links() for relationship links.
class CarPresenter extends Presenter {
static type = 'cars'
relationships() {
return { motor: MotorPresenter }
}
selfLinks(instance) {
return '/cars/' + this.id(instance)
}
links(instance) {
return {
motor: {
self: this.selfLinks(instance) + '/relationships/motor',
related: this.selfLinks(instance) + '/motor',
},
}
}
}
Render options
Pass meta and links as top-level document properties:
ItemPresenter.render(items, {
meta: { total: 100, page: 1 },
links: { self: '/items?page=1', next: '/items?page=2' },
})
Rendering null produces { data: null }. Arrays produce { data: [...] }.
Store (Standard)
Parses JSON API documents, resolves relationships, and caches models.
const store = new Store()
Syncing data
// sync: returns model for single resource, array for collection
const event = store.sync({ data: { type: 'events', id: '1', attributes: { name: 'Demo' } } })
// syncAll: always returns array
const events = store.syncAll(jsonApiDocument)
// retrieve: returns single model or null (type-filtered)
const event = store.retrieve('events', jsonApiDocument)
// retrieveAll: always returns array, filtered by type
const images = store.retrieveAll('images', jsonApiDocument)
Querying cached data
store.find('events', 1) // single model or null
store.findAll('events') // all cached events
store.remove('events', 1) // remove one
store.remove('events') // remove all of type
store.reset() // clear everything
Relationships resolve automatically
store.sync({
data: { type: 'events', id: '1', relationships: { images: { data: [{ type: 'images', id: '2' }] } } },
included: [{ type: 'images', id: '2', attributes: { url: 'pic.jpg' } }],
})
const event = store.find('events', '1')
event.images[0].url // 'pic.jpg'
Schema validation
Accepts any Zod-like schema (must have parse/safeParse methods). Use .passthrough() on Zod objects so extra attributes aren't stripped.
import { z } from 'zod'
const eventSchema = z
.object({
id: z.string(),
name: z.string(),
})
.passthrough()
const store = new Store({
schemas: { events: eventSchema },
strict: true, // throws on validation error; false collects in store.validationErrors
})
Accessing metadata via symbols
import { getType, getMeta, getLinks, getRelationshipLinks, getRelationshipMeta } from 'yayson/utils'
const event = store.find('events', '1')
getType(event) // 'events'
getMeta(event) // resource meta
getLinks(event) // resource links
getRelationshipLinks(event.images) // relationship links
getRelationshipMeta(event.images) // relationship meta
Store (Legacy)
For pre-JSON API 1.0 flat format. Import from yayson/legacy.
import yayson from 'yayson/legacy'
const { Store } = yayson()
const store = new Store({
types: { events: 'event', images: 'image' }, // plural -> singular mapping
})
Legacy data format
store.sync({
links: {
'event.images': { type: 'images' },
'images.event': { type: 'event' },
},
event: { id: 1, name: 'Demo', images: [2] },
images: [{ id: 2, event: 1, url: 'pic.jpg' }],
})
const event = store.find('event', 1)
event.images[0].url // 'pic.jpg'
The API methods (sync, syncAll, retrieve, retrieveAll, find, findAll, remove, reset) work the same as the standard Store. Schema validation is also supported.
Quick Reference
For detailed API reference including all method signatures, return types, and edge cases, see references/api.md.