[![Build Status](](
[![Hex Version](](
[![Deps Status](](
[![Inline docs](]( formatting of Elixir data structures suitable for serialization by
libraries such as Poison.

## Questions/Help

Please open an issue or message/mention @alanpeabody in the [Elixir Slack](

## Usage

See [documentation]( on hexdoc for full
serialization and usage details.

## Installation
Add JaSerializer to your application


defp deps do
    # ...
      {:ja_serializer, "~> x.x.x"}
    # ...

## Serializer Behaviour and DSL

defmodule MyApp.ArticleSerializer do
  use JaSerializer

  location "/articles/:id"
  attributes [:title, :tags, :body, :excerpt]

  has_one :author,
    serializer: PersonSerializer,
    include: true,
    field: :authored_by

  has_many :comments,
    links: [
      related: "/articles/:id/comments",
      self: "/articles/:id/relationships/comments"

  def comments(article, _conn) do

  def excerpt(article, _conn) do
    [first | _ ] = String.split(article.body, ".")

### Attributes

Attributes are defined as a list in the serializer module.
The serializer will use the given atom as the key by default.
You can also specify a custom method of attribute retrieval by defining a
<attribute_name>/2 method. The method will be passed the struct
and the connection.

### Relationships

Valid relationships are: `has_one`, `has_many`.
Use `has_one` for `belongs_to` type of relationships.
For each relationship, you can define the name and a variety of options.
Just like attributes, the serializer will use the given atom
to look up the relationship, unless you specify a custom retrieval method
OR provide a `field` option

#### Relationship options

* serializer - The serializer to use when serializing this resource
* include - boolean - true to always side-load this relationship
* field - custom field to use for relationship retrieval
* links - custom links to use in the `relationships` hash

### Direct Usage of Serializer

|> JaSerializer.format(struct, conn)
|> Poison.encode!

### Formatting options

The `format/4` method is able to take in options that can customize the
serialized payload.

#### Include

By specifying the `include` option, the serializer will only side-load
the relationships specified. This option should be a comma separated
list of relationships. Each relationship should be a dot separated path.

Example: `include: "author,"`

The format of this string should exactly match the one specified by the
[JSON-API spec](

Note: If specifying the `include` option, all "default" includes will
be ignored, and only the specified relationships included, per spec.

#### Fields

The `fields` option satisfies the [sparse fieldset]( portion of the spec. This options should
be a map of resource types whose value is a comma separated list of fields
to include.

Example: `fields: %{"articles" => "title,body", "comments" => "body"}`

If you're using Plug, you should be able to call `fetch_query_params(conn)`
and pass the result of `conn.query_params["fields"]` as this option.

## Phoenix Usage

Simply `use JaSerializer.PhoenixView` in your view (or in the Web module) and
define your serializer as above.

The `render("index.json-api", data)` and `render("show.json-api", data)` are defined
for you. You can just call render as normal from your controller.

By specifying `include`s when calling the render function, you can override
the `include: false` in the ArticleView.

defmodule PhoenixExample.ArticlesController do
  use PhoenixExample.Web, :controller

  def index(conn, _params) do
    render conn, "index.json-api", data: Repo.all(Article)

  def show(conn, %{"id" => id}) do
    article = Repo.get(Article, id) |> Repo.preload([:comments])
    render conn, "show.json-api", data: article,
      opts: [include: "comments"]

  def create(conn, %{"data" => data}) do
    attrs = JaSerializer.Params.to_attributes(data)
    changeset = Article.changeset(%Article{}, attrs)
    case Repo.insert(changeset) do
      {:ok, article} ->
        |> put_status(201)
        |> render("show.json-api", data: article)
      {:error, changeset} ->
        |> put_status(422)
        |> render(:errors, data: changeset)

defmodule PhoenixExample.ArticlesView do
  use PhoenixExample.Web, :view
  use JaSerializer.PhoenixView # Or use in web/web.ex

  attributes [:title]

  has_many :comments,
    serializer: PhoenixExample.CommentsView,
    include: false,
    identifiers: :when_included
  #has_many, etc.

## Configuration

To use the Phoenix `accepts` plug you must configure Plug to handle the
"application/vnd.api+json" mime type and Phoenix to serialize json-api with

Depending on your version of Plug add the following to `config.exs`:

Plug ~> "1.2.0"
config :phoenix, :format_encoders,
  "json-api": Poison

config :mime, :types, %{
  "application/vnd.api+json" => ["json-api"]

And then re-compile mime: (per:

mix deps.clean mime --build
mix deps.get

Plug < "1.2.0"
config :phoenix, :format_encoders,
  "json-api": Poison

config :plug, :mimes, %{
  "application/vnd.api+json" => ["json-api"]

And then re-compile plug: (per:

mix deps.clean plug --build
mix deps.get

And then add json api to your plug pipeline.

pipeline :api do
  plug :accepts, ["json-api"]

For strict content-type/accept enforcement and to auto add the proper
content-type to responses add the JaSerializer.ContentTypeNegotiation plug.

To normalize attributes to underscores include the JaSerializer.Deserializer

pipeline :api do
  plug :accepts, ["json-api"]
  plug JaSerializer.ContentTypeNegotiation
  plug JaSerializer.Deserializer

If you're rendering JSON API errors, like `404.json-api`, then you _must_ add `json-api`
to the `accepts` of your `render_errors` within your existing configuration in `config.exs`, like so:

config :phoenix, PhoenixExample.Endpoint,
  render_errors: [view: PhoenixExample.ErrorView, accepts: ~w(html json json-api)]

## Testing controllers

Set the right headers in `setup` and when passing parameters to put and post requests,
you should pass them as a binary. That is because for map and list parameters,
the content-type will be automatically changed to multipart.

defmodule Sample.SomeControllerTest do
  use Sample.ConnCase

  setup %{conn: conn} do
    conn =
      |> put_req_header("accept", "application/vnd.api+json")
      |> put_req_header("content-type", "application/vnd.api+json")

    {:ok, conn: conn}

  test "create action", %{conn: conn} do
    params = Poison.encode!(%{data: %{attributes: @valid_attrs}})
    conn = post conn, "/some_resource", params



## JSON API Generator

Use our built in generator to get up and running quickly. It uses the same format as the phoenix json generator.

mix ja_serializer.gen.phoenix_api Checkbox checkboxes description:string checked:boolean list_id:references:lists

Want to tweak our templates? Insert your own under 'priv/templates/ja_serializer.gen.phoenix_api/' and we'll use yours instead.

## Pagination

JaSerializer provides page based pagination integration with
[Scrivener]( or custom pagination
by passing your owns links in.

### Custom

JaSerializer allows custom pagination via the `page` option. The `page` option
expects to receive a `Map` with URL values for `first`, `next`, `prev`,
and `last`.

For example:

page = %{
  first: "[cursor]=1&page[per]=20",
  prev: nil
  next: "[cursor]=20&page[per]=20",
  last: "[cursor]=60&page[per]=20"

# Direct call
JaSerializer.format(MySerializer, collection, conn, page: page)

# In Phoenix Controller
render conn, data: collection, opts: [page: page]

#### Builder

You can build the pagination links with

Simply pass in the following:

links ={
  number: 2,
  size: 10,
  total: 20

See `JaSerializer.Builder.PaginationLinks` for how to customize.

### Scrivener Integration

If you are using Scrivener for pagination, all you need to do is pass the
results of `paginate/2` to your serializer.

page = MyRepo.paginate(MyModel,

# Direct call
JaSerializer.format(MySerializer, page, conn, [])

# In Phoenix controller
render conn, data: page

When integrating with Scrivener, the URLs generated will be based on the
`Plug.Conn`'s path. This can be overridden by passing in the `page[:base_url]`

render conn, data: page, opts: [page: [base_url: ""]]

You can also configure `ja_serializer` to use a global default URL
base for all links.

config :ja_serializer,
  scrivener_base_url: ""

*Note*: The resulting URLs will use the JSON-API recommended `page` query

Example URL:

### Meta Data

JaSerializer allows adding top level meta information via the `meta` option. The `meta` option
expects to receive a `Map` containing the data which will be rendered under the top level meta key.

meta_data = %{
  "key" => "value"

# Direct call
JaSerializer.format(MySerializer, data, conn, meta: meta_data)

# In Phoenix controller
render conn, data: data, opts: [meta: meta_data]

## Customization

### Key Format (for Attribute, Relationship and Query Param)

By default keys are `dash-erized` as per the recommendation, but
keys can be customized via config.

In your `config.exs` file:

config :ja_serializer,
  key_format: :underscored

You may also pass custom function for serialization and a second optional one for deserialization. Both accept a single binary argument:

defmodule MyStringModule do
  def camelize(key), do: key #...
  def underscore(key), do: key #...

config :ja_serializer,
  key_format: {:custom, MyStringModule, :camelize, :underscore}

If you've already compiled your code, be sure to run `mix deps.clean ja_serializer && mix deps.get`

### Custom Attribute Value Formatters

When serializing attribute values more complex than string, numbers, atoms or
list of those things it is recommended to implement a custom formatter.

To implement a custom formatter:

defimpl JaSerializer.Formatter, for: [MyStruct] do
  def format(struct), do: struct

### Pluralizing All Types By Default

You can opt-in to pluralizing all types for default:

config :ja_serializer,
  pluralize_types: true

## Complimentary Libraries

* [JaResource]( - WIP behaviour for creating JSON-API controllers in Phoenix.
* [voorhees]( - Testing tool for JSON API responses
* [inquisitor]( - Composable query builder for Ecto
* [scrivener]( - Ecto pagination

## License

JaSerializer source code is released under Apache 2 License. Check LICENSE
file for more information.