JaSerializer.Serializer behaviour

Define a serialization schema.

Provides has_many/2, has_one/2, attributes1 and location1 macros to define how your model (struct or map) will be rendered in the JSONAPI.org 1.0 format.

Defines format/1, format/2 and format/3 used to convert models (and list of models) for encoding in your JSON library of choice.

Example

defmodule PostSerializer do
  use JaSerializer

  location "/posts/:id"
  attributes [:title, :body, :excerpt, :tags]
  has_many :comments, link: "/posts/:id/comments"
  has_one :author, serializer: PersonSerializer, include: true

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

post = %Post{
  id: 1,
  title: "jsonapi.org + Elixir = Awesome APIs",
  body: "so. much. awesome.",
  author: %Person{name: "Alan"}
}

post
|> PostSerializer.format
|> Poison.encode!
Source

Summary

attributes(atts)

Defines a list of attributes to be included in the payload

has_many(name, opts \\ [])

Add a has_many relationship to be serialized

has_one(name, opts \\ [])

See documentation for has_many/2

location(uri)

Defines the canoical path for retrieving this resource

Types

id :: String.t | Integer

model :: Map

Macros

attributes(atts)

Defines a list of attributes to be included in the payload.

An overrideable function for each attribute is generated with the same name as the attribute. The function’s default behavior is to retrieve a field with the same name from the model.

For example, if you have attributes [:body] a function body/2 is defined on the serializer with a default behavior of Map.get(model, :body).

Source
has_many(name, opts \\ [])

Add a has_many relationship to be serialized.

Relationships may be included in any of three composeable ways:

  • Links
  • Resource Identifiers
  • Includes

Relationship Source

When you define a relationship, a function is defined of the same name in the serializer module. This function is overrideable but by default attempts to access a field of the same name as the relationship on the map/struct passed in. The field may be changed using the field option.

For example if you have_many :comments a function comments2 is defined which calls Dict.get(model, :comments) by default.

Link based relationships

Specify a uri which responds with the related resources. See location/1 for defining uris.

The relationship source is disregarded when linking.

defmodule PostSerializer do
  use JaSerializer

  has_many :comments, link: "/posts/:id/comments"
end

Resource Identifier Relationships

Adds a list of id and type pairs to the response with the assumption the API consumer can use them to retrieve the related resources as needed.

The relationship source should return either a list of ids or maps/structs that have an id field.

defmodule PostSerializer do
  use JaSerializer

  has_many :comments, type: "comments"

  def comments(post, _conn) do
    post |> PostModel.get_comments |> Enum.map(&(&1.id))
  end
end

Included Relationships

Adds a list of id and type pairs, just like Resource Indentifier relationships, but also adds the full serialized resource to the response to be “sideloaded” as well.

The relationship source should return a list of maps/structs.

defmodule PostSerializer do
  use JaSerializer

  has_many :comments, serializer: CommentSerializer, include: true

  def comments(post, _conn) do
    post |> PostModel.get_comments
  end
end

defmodule CommentSerializer do
  use JaSerializer

  has_one :post, field: :post_id, type: "posts"
  attributes [:body]
end
Source
has_one(name, opts \\ [])

See documentation for has_many/2.

API is the exact same.

Source
location(uri)

Defines the canoical path for retrieving this resource.

String Examples

String may be either a full url or a relative path. Path segments begining with a colin are called as functions on the serializer with the model and conn passed in.

defmodule PostSerializer do
  use JaSerializer

  location "/posts/:id"
end

defmodule CommentSerializer do
  use JaSerializer

  location "http://api.example.com/posts/:post_id/comments/:id"

  def post_id(comment, _conn), do: comment.post_id
end

Atom Example

When an atom is passed in it is assumed it is a function that will return a relative or absolute path.

defmodule PostSerializer do
  use JaSerializer
  import MyPhoenixApp.Router.Helpers

  location :post_url

  def post_url(post, conn) do
    #TODO: Verify conn can be used here instead of Endpoint
    post_path(conn, :show, post.id)
  end
end
Source

Callbacks

attributes/2

Specs:

  • attributes(model, Plug.Conn.t) :: map

Returns a map of attributes to be mapped.

The default implimentation relies on the attributes/1 macro to define which fields to be included in the map.

defmodule UserSerializer do
  attributes [:email, :name, :is_admin]
end

UserSerializer.attributes(user, conn)
# %{email: "...", name: "...", is_admin: "..."}

You may override this method and use super to filter attributes:

defmodule UserSerializer do
  attributes [:email, :name, :is_admin]

  def attributes(model, conn) do
    attrs = super(model, conn)
    if conn.assigns[:current_user].is_admin do
      attrs
    else
      Map.take(attrs, [:email, :name])
    end
  end
end

UserSerializer.attributes(user, conn)
# %{email: "...", name: "..."}

You may also skip using the attributes/1 macro altogether in favor of just defining attributes/2.

defmodule UserSerializer do
  def attributes(model, conn) do
    Map.take(model, [:email, :name])
  end
end

UserSerializer.attributes(user, conn)
# %{email: "...", name: "..."}
Source
id/2

Specs:

The id to be used in the resource object.

http://jsonapi.org/format/#document-resource-objects

Default implimentation attempts to get the :id field from the model.

To override simply define the id function:

def id(model, _conn), do: model.slug
Source
type/0

Specs:

The type to be used in the resource object.

http://jsonapi.org/format/#document-resource-objects

Default implimentation attempts to infer the type from the serializer module’s name. For example:

MyApp.UserView becomes "user"
MyApp.V1.Serializers.Post becomes "post"
MyApp.V1.CommentsSerializer becomes "comments"

To override simply define the type function:

def type, do: "category"
Source