Protocol controlling how a value is encoded to JSON.
Deriving
The protocol allows leveraging the Elixir's @derive feature
to simplify protocol implementation in trivial cases. Accepted
options are:
:only- encodes only values of specified keys.:except- encodes all struct fields except specified keys.
By default all keys except the :__struct__ key are encoded.
Example
Let's assume a struct that represents a person:
defmodule Person do
@derive {RustyJson.Encoder, only: [:name, :age]}
defstruct [:name, :age, :private_data]
endThe @derive generates an optimized implementation that pattern-matches
struct fields and uses one of two fast paths:
- Small structs: builds JSON iodata with compile-time collapsed keys
(no runtime
Map.from_struct,Map.to_list, or key escaping). - Larger structs: delegates to a NIF-accelerated path using pre-escaped keys, with an automatic fallback to the same iodata path when the NIF would not be beneficial.
Explicit Implementation
If you need full control, implement the protocol directly.
Implementations should return iodata (matching Jason's contract)
by calling RustyJson.Encode functions:
defimpl RustyJson.Encoder, for: Money do
def encode(%Money{amount: amount, currency: currency}, opts) do
RustyJson.Encode.map(%{amount: amount, currency: to_string(currency)}, opts)
end
endFor backwards compatibility, returning a plain map is also supported and will be re-encoded automatically:
defimpl RustyJson.Encoder, for: Money do
def encode(%Money{amount: amount, currency: currency}, _opts) do
%{amount: amount, currency: to_string(currency)}
end
endReturn Value Contract
Implementations must return one of:
- iodata (preferred) — built via
RustyJson.Encodefunctions (Encode.map/2,Encode.list/2,Encode.value/2, etc.). This is the most efficient path and matches Jason's contract. - a plain map — re-encoded automatically as a JSON object. Supported for backwards compatibility with a small overhead.
Do not return bare Elixir terms (atoms, integers, plain lists,
nil, unquoted strings) from encode/2. These are not valid iodata
and will produce silently invalid JSON or raise ArgumentError
downstream.
This is an intentional design choice shared with Jason: the protocol
is a low-level iodata contract that does not validate return values.
Validating every return would require runtime type inspection on every
value in the tree — for a large payload with millions of values, that
overhead is measurable. Instead, the contract trusts implementations
to return valid iodata (which @derive guarantees automatically) and
passes results through with zero checking. Correct implementations
get maximum performance; incorrect implementations get silent
corruption rather than a helpful error.
Use RustyJson.Encode functions to produce correctly formatted JSON:
# WRONG: bare string — produces unquoted JSON
def encode(%Name{value: v}, _opts), do: v
# RIGHT: properly quoted JSON string
def encode(%Name{value: v}, opts), do: RustyJson.Encode.string(v, opts)
# WRONG: plain list — treated as iodata bytes, not a JSON array
def encode(%Ids{list: l}, _opts), do: l
# RIGHT: JSON array
def encode(%Ids{list: l}, opts), do: RustyJson.Encode.list(l, opts)Fallback Behavior
Structs without an explicit RustyJson.Encoder implementation raise
Protocol.UndefinedError. Custom types must explicitly opt in to encoding
via @derive RustyJson.Encoder or defimpl RustyJson.Encoder.
Summary
Functions
Encodes value to JSON iodata or a plain map.
Types
@type opts() :: RustyJson.Encode.opts()
Encoder options passed from RustyJson.encode!/2.
This is an opaque value matching RustyJson.Encode.opts(). Pass it as-is to
RustyJson.Encode functions (value/2, map/2, string/2, etc.) inside
custom encoder implementations. Do not inspect or destructure this value.
@type t() :: term()
All the types that implement this protocol.
Functions
Encodes value to JSON iodata or a plain map.
Derived implementations return iodata directly (pre-serialized JSON).
Custom implementations may return either iodata (preferred, via
RustyJson.Encode functions) or a plain map which will be
re-encoded automatically.
Other return types (nil, atoms, bare strings, plain lists) are not supported and will produce invalid JSON or raise. See the "Return Value Contract" section in the moduledoc.
The opts parameter carries encoding context (:escape, :maps) from
RustyJson.encode!/2. Pass it to RustyJson.Encode functions as-is.