Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/vt-elixir/ja_serializer
JSONAPI.org Serialization in Elixir.
https://github.com/vt-elixir/ja_serializer
elixir json-api
Last synced: 25 days ago
JSON representation
JSONAPI.org Serialization in Elixir.
- Host: GitHub
- URL: https://github.com/vt-elixir/ja_serializer
- Owner: vt-elixir
- License: other
- Created: 2015-06-14T18:57:59.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2024-07-31T13:42:53.000Z (3 months ago)
- Last Synced: 2024-10-01T15:43:53.265Z (about 1 month ago)
- Topics: elixir, json-api
- Language: Elixir
- Homepage:
- Size: 505 KB
- Stars: 640
- Watchers: 16
- Forks: 148
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- freaking_awesome_elixir - Elixir - JSONAPI.org Serialization in Elixir. (JSON)
README
JaSerializer
============[![Build Status](https://travis-ci.org/vt-elixir/ja_serializer.svg?branch=master)](https://travis-ci.org/vt-elixir/ja_serializer)
[![Hex Version](https://img.shields.io/hexpm/v/ja_serializer.svg)](https://hex.pm/packages/ja_serializer)
[![Inline docs](http://inch-ci.org/github/vt-elixir/ja_serializer.svg)](http://inch-ci.org/github/vt-elixir/ja_serializer)jsonapi.org formatting of Elixir data structures suitable for serialization by
libraries such as Poison.## Usage
See [documentation](http://hexdocs.pm/ja_serializer/) on hexdoc for full
serialization and usage details.## Installation
Add JaSerializer to your applicationmix.deps
```elixir
defp deps do
[
# ...
{:ja_serializer, "~> x.x.x"}
# ...
]
end
```## Serializer Behaviour and DSL
```elixir
defmodule MyApp.ArticleSerializer do
use JaSerializerlocation "/articles/:id"
attributes [:title, :tags, :body, :excerpt]has_one :author,
serializer: PersonSerializer,
include: true,
field: :authored_byhas_many :comments,
links: [
related: "/articles/:id/comments",
self: "/articles/:id/relationships/comments"
]def comments(article, _conn) do
Comment.for_article(article)
enddef excerpt(article, _conn) do
[first | _ ] = String.split(article.body, ".")
first
end
end
```### 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
/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
```elixir
MyApp.ArticleSerializer
|> 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,comments.author"`
The format of this string should exactly match the one specified by the
[JSON-API spec](http://jsonapi.org/format/#fetching-includes)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](http://jsonapi.org/format/#fetching-sparse-fieldsets) 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
For an example of starting with Phoenix's JSON generator and updating
to work with JaSerializer, see [Getting Started with Phoenix](https://github.com/vt-elixir/ja_serializer/wiki/Getting-Started-with-Phoenix).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.```elixir
defmodule PhoenixExample.ArticlesController do
use PhoenixExample.Web, :controllerdef index(conn, _params) do
render conn, "index.json-api", data: Repo.all(Article)
enddef show(conn, %{"id" => id}) do
article = Repo.get(Article, id) |> Repo.preload([:comments])
render conn, "show.json-api", data: article,
opts: [include: "comments"]
enddef create(conn, %{"data" => data}) do
attrs = JaSerializer.Params.to_attributes(data)
changeset = Article.changeset(%Article{}, attrs)
case Repo.insert(changeset) do
{:ok, article} ->
conn
|> put_status(201)
|> render("show.json-api", data: article)
{:error, changeset} ->
conn
|> put_status(422)
|> render(:errors, data: changeset)
end
end
enddefmodule PhoenixExample.ArticlesView do
use PhoenixExample.Web, :view
use JaSerializer.PhoenixView # Or use in web/web.exattributes [:title]
has_many :comments,
serializer: PhoenixExample.CommentsView,
include: false,
identifiers: :when_included
#has_many, etc.
end
```## 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
Poison.Depending on your version of Plug add the following to `config.exs`:
Plug ~> "1.2.0"
```elixir
config :phoenix, :format_encoders,
"json-api": Poisonconfig :mime, :types, %{
"application/vnd.api+json" => ["json-api"]
}
```And then re-compile mime: (per: https://hexdocs.pm/mime/MIME.html)
```shell
mix deps.clean mime --build
mix deps.get
```Plug < "1.2.0"
```elixir
config :phoenix, :format_encoders,
"json-api": Poisonconfig :plug, :mimes, %{
"application/vnd.api+json" => ["json-api"]
}
```And then re-compile plug: (per: https://hexdocs.pm/plug/1.1.3/Plug.MIME.html)
```shell
mix deps.clean plug --build
mix deps.get
```And then add json api to your plug pipeline.
```elixir
pipeline :api do
plug :accepts, ["json-api"]
end
```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
plug.```elixir
pipeline :api do
plug :accepts, ["json-api"]
plug JaSerializer.ContentTypeNegotiation
plug JaSerializer.Deserializer
end
```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:```elixir
config :phoenix, PhoenixExample.Endpoint,
render_errors: [view: PhoenixExample.ErrorView, accepts: ~w(html json json-api)]
```If you're rendering both JSON-API and HTML, you need to include the `html` option in the config:
```elixir
config :phoenix, :format_encoders,
html: Phoenix.Template.HTML,
"json-api": Poison
```## 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.```elixir
defmodule Sample.SomeControllerTest do
use Sample.ConnCasesetup %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/vnd.api+json")
|> put_req_header("content-type", "application/vnd.api+json"){:ok, conn: conn}
endtest "create action", %{conn: conn} do
params = Poison.encode!(%{data: %{attributes: @valid_attrs}})
conn = post conn, "/some_resource", params...
end...
end
```## Pagination
JaSerializer provides page based pagination integration with
[Scrivener](https://github.com/drewolson/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:
```elixir
page = %{
first: "http://example.com/api/v1/posts?page[cursor]=1&page[per]=20",
prev: nil
next: "http://example.com/api/v1/posts?page[cursor]=20&page[per]=20",
last: "http://example.com/api/v1/posts?page[cursor]=60&page[per]=20"
}# Direct call
JaSerializer.format(MySerializer, collection, conn, page: page)# In Phoenix Controller
render conn, "index.json-api", data: collection, opts: [page: page]
```#### Builder
You can build the pagination links with
`JaSerializer.Builder.PaginationLinks.build/2`Simply pass in the following:
```elixir
links =
JaSerializer.Builder.PaginationLinks.build(
%{
number: 2,
size: 10,
total: 20
},
conn
)
```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.```elixir
page = MyRepo.paginate(MyModel, params.page)# Direct call
JaSerializer.format(MySerializer, page, conn, [])# In Phoenix controller
render conn, "index.json-api", 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]`
option.```elixir
render conn, "index.json-api", data: page, opts: [base_url: "http://example.com/foos"]
```You can also configure `ja_serializer` to use a global default URL
base for all links.```elixir
config :ja_serializer,
page_base_url: "http://example.com:4000/v1/"
```*Note*: The resulting URLs will use the JSON-API recommended `page` query
param.Example URL:
`http://example.com:4000/v1/posts?page[page]=2&page[page-size]=50`### 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.```elixir
meta_data = %{
"key" => "value"
}# Direct call
JaSerializer.format(MySerializer, data, conn, meta: meta_data)# In Phoenix controller
render conn, "index.json-api", data: data, opts: [meta: meta_data]
```## Customization
### Key Format (for Attribute, Relationship and Query Param)
By default keys are `dash-erized` as per the JSON:API 1.0 recommendation, but keys can be customized via config.
In your `config.exs` file you can use `camel_cased` recommended by upcoming JSON:API 1.1:
```elixir
config :ja_serializer,
key_format: :camel_cased
```Or `underscored`:
```elixir
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:
```elixir
defmodule MyStringModule do
def camelize(key), do: key #...
def underscore(key), do: key #...
endconfig :ja_serializer,
key_format: {:custom, MyStringModule, :camelize, :underscore}
```### 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:
```elixir
defimpl JaSerializer.Formatter, for: [MyStruct] do
def format(struct), do: struct
end
```### Pluralizing All Types By Default
You can opt-in to pluralizing all types for default:
```elixir
config :ja_serializer,
pluralize_types: true
```## Complimentary Libraries
* [JaResource](https://github.com/vt-elixir/ja_resource) - WIP behaviour for creating JSON-API controllers in Phoenix.
* [voorhees](https://github.com/danmcclain/voorhees) - Testing tool for JSON API responses
* [inquisitor](https://github.com/DockYard/inquisitor) - Composable query builder for Ecto
* [scrivener](https://github.com/drewolson/scrivener) - Ecto pagination## License
JaSerializer source code is released under Apache 2 License. Check LICENSE
file for more information.