---
outline: deep
---

# DF0020: Non-JSON Value in JSON-Serializable RPC

## Message

> RPC function "`{name}`" declares `jsonSerializable: true` but the value at "`{path}`" is a `{type}`.

## Cause

The function is declared `jsonSerializable: true`, which means its args and return value are encoded with strict `JSON.stringify` (both on the wire and in build dumps). The strict serializer rejects any value that JSON cannot round-trip losslessly:

- `Map`, `Set`, `WeakMap`, `WeakSet`
- `Date` (silently coerced to ISO string by JSON)
- `BigInt`
- circular references
- non-plain class instances
- `undefined` leaves
- `Symbol`
- `Function`

When the strict serializer encounters one of these, it throws synchronously at the offending call rather than producing a corrupt payload.

## Example

```ts
defineRpcFunction({
  name: 'my-plugin:graph',
  jsonSerializable: true,
  handler: () => ({
    nodes: new Map([['a', 1]]), // ← throws DF0020 with type=Map, path="nodes"
  }),
})
```

## Fix

Either drop `jsonSerializable: true` so the function uses `structured-clone-es` (round-trips `Map`, `Set`, etc.):

```ts
defineRpcFunction({
  name: 'my-plugin:graph',
  // jsonSerializable: false (default) — Map/Set survive the wire and the dump
  handler: () => ({
    nodes: new Map([['a', 1]]),
  }),
})
```

Or convert the payload to a JSON-safe shape (e.g. an array of entries, an ISO string, a plain object) before returning. Note: removing `jsonSerializable: true` also disables `agent` exposure; if you need MCP, you must use a JSON-safe shape.

## Source

- [`packages/devframe/src/rpc/serialization.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/rpc/serialization.ts) — the strict JSON serializer throws `DF0020` (with the offending path and runtime type) when a `jsonSerializable: true` payload contains a non-JSON value.
