yayson

star 186

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.

confetti By confetti schedule Updated 6/5/2026

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 as data: [] instead of data: 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 if links() configures one for that key). An explicit null on the instance still renders as data: nulloptional distinguishes "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.

Install via CLI
npx skills add https://github.com/confetti/yayson --skill yayson
Repository Details
star Stars 186
call_split Forks 28
navigation Branch main
article Path SKILL.md
More from Creator