Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool

Elixir/Phoenix JSON Formatter Implementations

If you need to format JSON in a modern Phoenix app, start with the response shape you want and then choose the right layer. In Phoenix 1.7 and 1.8, the usual pattern is to keep endpoint-specific formatting in a *JSON module such as MyAppWeb.UserJSON, use Jason.Encoder only when a struct truly needs a reusable default JSON representation, and reserve pretty-printed JSON for debugging, logs, or exported files.

That split keeps controllers thin, avoids leaking internal schema fields, and makes it easier to return different shapes for list, detail, admin, and public API responses.

What Phoenix Uses Today

Phoenix uses the configured JSON library to serialize maps, lists, and other encodable values. In most projects that library is Jason. For JSON endpoints, current Phoenix apps typically render through modules like MyAppWeb.UserJSON and call render(conn, :show, user: user) or render(conn, :index, users: users) from the controller.

This is more maintainable than pushing all formatting into structs, because the response shape belongs to the endpoint rather than to the database schema.

Recommended Phoenix Pattern

defmodule MyAppWeb.UserController do
  use MyAppWeb, :controller
  alias MyApp.Accounts

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, :show, user: user)
  end

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, :index, users: users)
  end
end

defmodule MyAppWeb.UserJSON do
  alias MyApp.Accounts.User

  def show(%{user: user}) do
    %{data: data(user)}
  end

  def index(%{users: users}) do
    %{data: Enum.map(users, &data/1)}
  end

  defp data(%User{} = user) do
    %{
      id: user.id,
      email: user.email,
      isActive: user.is_active,
      insertedAt: DateTime.to_iso8601(user.inserted_at)
    }
  end
end

If you are still using older examples with render("user.json", ...), treat them as legacy Phoenix style. The current JSON module approach is the clearer default for new code.

When To Use Jason.Encoder

Implement or derive Jason.Encoder when a struct needs one stable JSON representation across many places, for example for internal messages, cached payloads, or a very small API. Do not reach for it first if the same struct appears in multiple endpoint shapes.

Safe Default With @derive

For a schema or struct that only needs field selection, put @derive directly above the schema or defstruct. That is the valid concise pattern. It should not be wrapped in a custom defimpl block.

Example: Ecto Schema With @derive

defmodule MyApp.Accounts.User do
  use Ecto.Schema

  @derive {Jason.Encoder, only: [:id, :email, :is_active, :inserted_at]}
  schema "users" do
    field :email, :string
    field :is_active, :boolean, default: false
    field :hashed_password, :string
    timestamps(type: :utc_datetime)
  end
end

This keeps sensitive fields such as hashed_password out of the JSON output by default.

Explicit Encoder For Real Transformations

If you need renamed keys, computed values, or nested custom output, write a real protocol implementation instead of stretching @derive too far.

Example: Custom Jason.Encoder Implementation

defmodule MyApp.Billing.InvoiceTotal do
  defstruct [:amount_cents, :currency]
end

defimpl Jason.Encoder, for: MyApp.Billing.InvoiceTotal do
  def encode(%{amount_cents: cents, currency: currency}, opts) do
    Jason.Encode.map(
      %{
        amount: cents / 100,
        currency: currency
      },
      opts
    )
  end
end

Keep protocol implementations focused on reusable defaults. If one controller needs a different shape, that controller should usually render through a JSON module instead.

Formatting Directly With json(conn, data)

For small endpoints like health checks, feature flags, or webhook acknowledgements, you do not need a dedicated JSON module. Returning a map straight from the controller is fine.

Example: Small Controller Response

def health(conn, _params) do
  json(conn, %{
    status: "ok",
    checkedAt: DateTime.utc_now() |> DateTime.to_iso8601()
  })
end

Once the payload grows past a couple of fields or you need multiple shapes for the same resource, move that formatting into a dedicated JSON module.

Pretty Printing: Useful For Debugging, Rarely For APIs

If you need human-readable JSON, use Jason.encode!/2 with pretty: true. That is ideal for logs, local debugging, fixtures, copied examples, or downloaded files. It is usually not worth sending pretty-printed JSON from production API endpoints because it adds bytes without helping most clients.

Example: Generate Readable JSON

payload = MyAppWeb.UserJSON.show(%{user: user})

pretty_json =
  Jason.encode!(payload, pretty: true)

IO.puts(pretty_json)

If a client truly requires pretty output, encode the response yourself and send it explicitly. Do that on purpose, not as a global default.

Common Pitfalls

  • Put @derive before schema or defstruct. Do not create a fake defimpl block just to configure derivation.
  • Be deliberate about key style. Atom keys are fine when the JSON shape matches Elixir names, but explicit string keys are clearer when you need exact camelCase output.
  • Do not serialize sensitive fields by accident. Password hashes, tokens, internal flags, and admin-only attributes should be excluded on purpose.
  • Preload associations before rendering them. JSON modules are a good place to make missing preload problems obvious.
  • Normalize timestamps, decimals, and other non-trivial values intentionally so API consumers get a stable format.

Practical Rule Of Thumb

Use MyAppWeb.*JSON modules for most Phoenix API responses, use json(conn, data) for tiny one-off payloads, and use Jason.Encoder only when a struct genuinely deserves one shared JSON representation. That combination stays close to current Phoenix conventions and scales better than trying to solve every formatting problem at the protocol layer.

Need help with your JSON?

Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool