# Ext-JSON


Ext-JSON is the standard JSON serialization layer for Primebrick backend
services. It uses `json-bigint` with `useNativeBigInt: true` to preserve
`bigint` values across the JSON wire format.

## Why Ext-JSON exists

Standard `JSON.stringify()` throws on `bigint`:

```ts
JSON.stringify({ id: 42n });
// TypeError: Do not know how to serialize a BigInt
```

Primebrick uses native `bigint` for all integer IDs (UUIDs are strings, but
numeric IDs, timestamps, and counters are `bigint`). Ext-JSON makes the types
predictable across the wire format:

| JSON value | Wire format | Parsed TS type |
|------------|-------------|----------------|
| `42n` (bigint) | `42` | `bigint` |
| `42` (small int) | `42` | `bigint` (reviver forces it) |
| `3.14` (float) | `3.14` | `number` |
| `1e5` (scientific) | `100000` | `bigint` (integer) |
| `"alice"` (string) | `"alice"` | `string` |
| `true` (boolean) | `true` | `boolean` |
| `null` | `null` | `null` |

The rule is simple: **every integer is `bigint`, every float is `number`**.
No `number | bigint` ambiguity — the type is determined by whether the value
has a decimal point, not by its magnitude.

## The reviver

`extJsonParse` uses a reviver that forces every integer to `bigint`:

```ts
extJsonParse(text, (_key, value) => {
  if (typeof value === "number" && Number.isInteger(value)) {
    return BigInt(value);
  }
  return value;
});
```

`json-bigint`'s `alwaysParseAsBig` option would do this, but it is broken for
floats in v1.0.0 — so the SDK uses a reviver instead. The reviver is the
reason `42` (a small integer that `json-bigint` returns as `number`) becomes
`bigint` on the receiving end, matching the `42n` the sender wrote.

## Usage

### Direct functions

```ts
import { extJsonStringify, extJsonParse } from "@primebrick/sdk";

const data = { entity_id: 42n, price: 3.14, name: "widget" };
const json = extJsonStringify(data);
// → '{"entity_id":42,"price":3.14,"name":"widget"}'

const parsed = extJsonParse<{ entity_id: bigint; price: number; name: string }>(json);
// parsed.entity_id → 42n (bigint)
// parsed.price → 3.14 (number)
```

### Round-trip type preservation

The round-trip preserves types exactly — `bigint` in, `bigint` out; `number`
in, `number` out. This is what makes the SDK's NATS™ and HTTP layers safe for
DAL rows that mix `bigint` IDs with `number` floats:

```ts
const original = {
  id: 42n,              // bigint  → bigint
  count: 7,             // number (integer) → bigint (reviver forces it)
  price: 3.14,          // number (float)   → number
  name: "widget",       // string → string
  active: true,         // boolean → boolean
  nested: { qty: 100n },// bigint nested → bigint
};

const roundTrip = extJsonParse<typeof original>(extJsonStringify(original));
// typeof roundTrip.id       → "bigint"
// typeof roundTrip.count    → "bigint"   (was a number, now bigint — by design)
// typeof roundTrip.price    → "number"
// typeof roundTrip.name     → "string"
// typeof roundTrip.nested.qty → "bigint"
```

The one asymmetry to know about: a plain `number` integer on the sender
becomes `bigint` on the receiver. This is intentional — it makes every
integer `bigint` everywhere, so consumers never write `number | bigint` union
types. If you send a `number` integer, expect a `bigint` back.

### Express middleware (BE)

```ts
import { extJsonMiddleware } from "@primebrick/sdk";

app.use(extJsonMiddleware());
// res.json() now uses Ext-JSON serialization for all subsequent responses
```

The middleware replaces `res.json()` with Ext-JSON serialization. Install it
once before any routes.

### NATS™ (microservices)

`NatsClient.publish()`, `subscribe()`, and `subscribeRequest()` use Ext-JSON
internally. Microservice code never calls `extJsonStringify` / `extJsonParse`
directly — just pass plain objects and receive plain objects.

## Sub-path import

If you need only the JSON functions without pulling in NATS™/auth code paths:

```ts
import { extJsonStringify, extJsonParse, extJsonMiddleware } from "@primebrick/sdk/json";
```

## Not for the frontend

The frontend has its own standalone wrapper (`src/lib/api-ext.ts`) that installs
`json-bigint` directly. The FE does not depend on `@primebrick/sdk`. Ext-JSON
is for BE and microservices only.

## Next steps

- [NATS Client](nats-client) — uses Ext-JSON internally
- [HTTP Server](http-server) — uses Ext-JSON for health and error responses
- [API Reference](api-reference) — extJsonStringify, extJsonParse, extJsonMiddleware
