Skip to content

Why coercion

And why coercion instead of codec or serializer/deserializer like io-ts (❤️) or @sinclair/typebox?

TL;DR – Because of Standard Schema

To me, Standard Schema is an excellent initiative and has great adoption. Building anything StandardSchema-compliant opens the door to tRPC, ts-rest and a ton of others tools.

From my point of view, the trade-off is acceptable and I am OK delegating serializing to clumsy APIs like JSON.stringify(data, replacer) and parsing to JSON.parse(jsonAsText, reviver).

See MDN for documentation of JSON.stringify's replacer and JSON.parse's reviver.

Example with JSON.stringify/parse

ts
import { x } from 'unhoax'

const itemQuantitySchema = x.number.refine('Quantity', (value) => value > 0)
const itemNameSchema = x.string

const shoppingListSchema = x.object({
  name: x.string,
  items: x.mapOf(itemNameSchema, itemQuantitySchema),
})

const shoppingListData = {
  name: 'Groceries',
  items: new Map([
    ['Apple', 5],
    ['Carrot', 8],
  ]),
}

const brokenJson = JSON.stringify(shoppingListData)
// '{ "name":"Groceries", "items": {} }' <-- not items in there !

const json = JSON.stringify(shoppingListData, (key, value) => {
  if (value instanceof Map) return [...value.entries()]
  return value
})
// '{ "name": "Groceries", "items": [["Apple", 5], ["Carrot", 8]] }'

const parsedJson = JSON.parse(json)
const shoppingList2 = shoppingListSchema.parse(parsedJson)
// No need to provide a `reviver`, the `x.mapOf` schema can
// parse entries directly. We only need the `replacer` to
// serialize a Map as JSON.