# `RustyJson.Encoder`
[🔗](https://github.com/jeffhuen/rustyjson/blob/v0.3.9/lib/encoder.ex#L1)

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]
    end

The `@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
    end

For 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
    end

## Return Value Contract

Implementations must return one of:

  * **iodata** (preferred) — built via `RustyJson.Encode` functions
    (`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`.

# `opts`

```elixir
@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.

# `t`

```elixir
@type t() :: term()
```

All the types that implement this protocol.

# `encode`

```elixir
@spec encode(t(), opts()) :: iodata() | term()
```

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
