{"id":13508429,"url":"https://github.com/vt-elixir/ja_serializer","last_synced_at":"2025-04-10T04:49:42.541Z","repository":{"id":1012181,"uuid":"37425270","full_name":"vt-elixir/ja_serializer","owner":"vt-elixir","description":"JSONAPI.org Serialization in Elixir.","archived":false,"fork":false,"pushed_at":"2024-07-31T13:42:53.000Z","size":517,"stargazers_count":639,"open_issues_count":17,"forks_count":147,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-03T02:28:06.351Z","etag":null,"topics":["elixir","json-api"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vt-elixir.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-06-14T18:57:59.000Z","updated_at":"2025-03-01T20:27:26.000Z","dependencies_parsed_at":"2024-07-31T17:12:46.518Z","dependency_job_id":null,"html_url":"https://github.com/vt-elixir/ja_serializer","commit_stats":{"total_commits":305,"total_committers":86,"mean_commits":3.546511627906977,"dds":0.5868852459016394,"last_synced_commit":"d716249c722e1e543e3da7a047e699200577df2b"},"previous_names":["agilionapps/ja_serializer"],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vt-elixir%2Fja_serializer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vt-elixir%2Fja_serializer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vt-elixir%2Fja_serializer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vt-elixir%2Fja_serializer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vt-elixir","download_url":"https://codeload.github.com/vt-elixir/ja_serializer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248161243,"owners_count":21057552,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","json-api"],"created_at":"2024-08-01T02:00:52.925Z","updated_at":"2025-04-10T04:49:42.519Z","avatar_url":"https://github.com/vt-elixir.png","language":"Elixir","funding_links":[],"categories":["JSON"],"sub_categories":[],"readme":"JaSerializer\n============\n\n[![Build Status](https://travis-ci.org/vt-elixir/ja_serializer.svg?branch=master)](https://travis-ci.org/vt-elixir/ja_serializer)\n[![Hex Version](https://img.shields.io/hexpm/v/ja_serializer.svg)](https://hex.pm/packages/ja_serializer)\n[![Inline docs](http://inch-ci.org/github/vt-elixir/ja_serializer.svg)](http://inch-ci.org/github/vt-elixir/ja_serializer)\n\njsonapi.org formatting of Elixir data structures suitable for serialization by\nlibraries such as Poison.\n\n## Usage\n\nSee [documentation](http://hexdocs.pm/ja_serializer/) on hexdoc for full\nserialization and usage details.\n\n## Installation\nAdd JaSerializer to your application\n\nmix.deps\n\n```elixir\ndefp deps do\n  [\n    # ...\n      {:ja_serializer, \"~\u003e x.x.x\"}\n    # ...\n  ]\nend\n```\n\n## Serializer Behaviour and DSL\n\n```elixir\ndefmodule MyApp.ArticleSerializer do\n  use JaSerializer\n\n  location \"/articles/:id\"\n  attributes [:title, :tags, :body, :excerpt]\n\n  has_one :author,\n    serializer: PersonSerializer,\n    include: true,\n    field: :authored_by\n\n  has_many :comments,\n    links: [\n      related: \"/articles/:id/comments\",\n      self: \"/articles/:id/relationships/comments\"\n    ]\n\n  def comments(article, _conn) do\n    Comment.for_article(article)\n  end\n\n  def excerpt(article, _conn) do\n    [first | _ ] = String.split(article.body, \".\")\n    first\n  end\nend\n```\n\n### Attributes\n\nAttributes are defined as a list in the serializer module.\nThe serializer will use the given atom as the key by default.\nYou can also specify a custom method of attribute retrieval by defining a\n\u003cattribute_name\u003e/2 method. The method will be passed the struct\nand the connection.\n\n### Relationships\n\nValid relationships are: `has_one`, `has_many`.\nUse `has_one` for `belongs_to` type of relationships.\nFor each relationship, you can define the name and a variety of options.\nJust like attributes, the serializer will use the given atom\nto look up the relationship, unless you specify a custom retrieval method\nOR provide a `field` option\n\n#### Relationship options\n\n* serializer - The serializer to use when serializing this resource\n* include - boolean - true to always side-load this relationship\n* field - custom field to use for relationship retrieval\n* links - custom links to use in the `relationships` hash\n\n### Direct Usage of Serializer\n\n```elixir\nMyApp.ArticleSerializer\n|\u003e JaSerializer.format(struct, conn)\n|\u003e Poison.encode!\n```\n\n### Formatting options\n\nThe `format/4` method is able to take in options that can customize the\nserialized payload.\n\n#### Include\n\nBy specifying the `include` option, the serializer will only side-load\nthe relationships specified. This option should be a comma separated\nlist of relationships. Each relationship should be a dot separated path.\n\nExample: `include: \"author,comments.author\"`\n\nThe format of this string should exactly match the one specified by the\n[JSON-API spec](http://jsonapi.org/format/#fetching-includes)\n\nNote: If specifying the `include` option, all \"default\" includes will\nbe ignored, and only the specified relationships included, per spec.\n\n#### Fields\n\nThe `fields` option satisfies the [sparse fieldset](http://jsonapi.org/format/#fetching-sparse-fieldsets) portion of the spec. This options should\nbe a map of resource types whose value is a comma separated list of fields\nto include.\n\nExample: `fields: %{\"articles\" =\u003e \"title,body\", \"comments\" =\u003e \"body\"}`\n\nIf you're using Plug, you should be able to call `fetch_query_params(conn)`\nand pass the result of `conn.query_params[\"fields\"]` as this option.\n\n## Phoenix Usage\n\nFor an example of starting with Phoenix's JSON generator and updating\nto work with JaSerializer, see [Getting Started with Phoenix](https://github.com/vt-elixir/ja_serializer/wiki/Getting-Started-with-Phoenix).\n\nSimply `use JaSerializer.PhoenixView` in your view (or in the Web module) and\ndefine your serializer as above.\n\nThe `render(\"index.json-api\", data)` and `render(\"show.json-api\", data)` are defined\nfor you. You can just call render as normal from your controller.\n\nBy specifying `include`s when calling the render function, you can override\nthe `include: false` in the ArticleView.\n\n```elixir\ndefmodule PhoenixExample.ArticlesController do\n  use PhoenixExample.Web, :controller\n\n  def index(conn, _params) do\n    render conn, \"index.json-api\", data: Repo.all(Article)\n  end\n\n  def show(conn, %{\"id\" =\u003e id}) do\n    article = Repo.get(Article, id) |\u003e Repo.preload([:comments])\n    render conn, \"show.json-api\", data: article,\n      opts: [include: \"comments\"]\n  end\n\n  def create(conn, %{\"data\" =\u003e data}) do\n    attrs = JaSerializer.Params.to_attributes(data)\n    changeset = Article.changeset(%Article{}, attrs)\n    case Repo.insert(changeset) do\n      {:ok, article} -\u003e\n        conn\n        |\u003e put_status(201)\n        |\u003e render(\"show.json-api\", data: article)\n      {:error, changeset} -\u003e\n        conn\n        |\u003e put_status(422)\n        |\u003e render(:errors, data: changeset)\n    end\n  end\nend\n\ndefmodule PhoenixExample.ArticlesView do\n  use PhoenixExample.Web, :view\n  use JaSerializer.PhoenixView # Or use in web/web.ex\n\n  attributes [:title]\n\n  has_many :comments,\n    serializer: PhoenixExample.CommentsView,\n    include: false,\n    identifiers: :when_included\n  #has_many, etc.\nend\n```\n\n## Configuration\n\nTo use the Phoenix `accepts` plug you must configure Plug to handle the\n\"application/vnd.api+json\" mime type and Phoenix to serialize json-api with\nPoison.\n\nDepending on your version of Plug add the following to `config.exs`:\n\nPlug ~\u003e \"1.2.0\"\n```elixir\nconfig :phoenix, :format_encoders,\n  \"json-api\": Poison\n\nconfig :mime, :types, %{\n  \"application/vnd.api+json\" =\u003e [\"json-api\"]\n}\n```\n\nAnd then re-compile mime: (per: https://hexdocs.pm/mime/MIME.html)\n\n```shell\nmix deps.clean mime --build\nmix deps.get\n```\n\nPlug \u003c \"1.2.0\"\n```elixir\nconfig :phoenix, :format_encoders,\n  \"json-api\": Poison\n\nconfig :plug, :mimes, %{\n  \"application/vnd.api+json\" =\u003e [\"json-api\"]\n}\n```\n\nAnd then re-compile plug: (per: https://hexdocs.pm/plug/1.1.3/Plug.MIME.html)\n\n```shell\nmix deps.clean plug --build\nmix deps.get\n```\n\nAnd then add json api to your plug pipeline.\n\n```elixir\npipeline :api do\n  plug :accepts, [\"json-api\"]\nend\n```\n\nFor strict content-type/accept enforcement and to auto add the proper\ncontent-type to responses add the JaSerializer.ContentTypeNegotiation plug.\n\nTo normalize attributes to underscores include the JaSerializer.Deserializer\nplug.\n\n```elixir\npipeline :api do\n  plug :accepts, [\"json-api\"]\n  plug JaSerializer.ContentTypeNegotiation\n  plug JaSerializer.Deserializer\nend\n```\n\nIf you're rendering JSON API errors, like `404.json-api`, then you _must_ add `json-api`\nto the `accepts` of your `render_errors` within your existing configuration in `config.exs`, like so:\n\n```elixir\nconfig :phoenix, PhoenixExample.Endpoint,\n  render_errors: [view: PhoenixExample.ErrorView, accepts: ~w(html json json-api)]\n```\n\nIf you're rendering both JSON-API and HTML, you need to include the `html` option in the config:\n\n```elixir\nconfig :phoenix, :format_encoders,\n  html: Phoenix.Template.HTML,\n  \"json-api\": Poison\n```\n\n## Testing controllers\n\nSet the right headers in `setup` and when passing parameters to put and post requests,\nyou should pass them as a binary. That is because for map and list parameters,\nthe content-type will be automatically changed to multipart.\n\n```elixir\ndefmodule Sample.SomeControllerTest do\n  use Sample.ConnCase\n\n  setup %{conn: conn} do\n    conn =\n      conn\n      |\u003e put_req_header(\"accept\", \"application/vnd.api+json\")\n      |\u003e put_req_header(\"content-type\", \"application/vnd.api+json\")\n\n    {:ok, conn: conn}\n  end\n\n  test \"create action\", %{conn: conn} do\n    params = Poison.encode!(%{data: %{attributes: @valid_attrs}})\n    conn = post conn, \"/some_resource\", params\n\n    ...\n  end\n\n  ...\nend\n```\n\n## Pagination\n\nJaSerializer provides page based pagination integration with\n[Scrivener](https://github.com/drewolson/scrivener) or custom pagination\nby passing your owns links in.\n\n### Custom\n\nJaSerializer allows custom pagination via the `page` option. The `page` option\nexpects to receive a `Map` with URL values for `first`, `next`, `prev`,\nand `last`.\n\nFor example:\n\n```elixir\npage = %{\n  first: \"http://example.com/api/v1/posts?page[cursor]=1\u0026page[per]=20\",\n  prev: nil\n  next: \"http://example.com/api/v1/posts?page[cursor]=20\u0026page[per]=20\",\n  last: \"http://example.com/api/v1/posts?page[cursor]=60\u0026page[per]=20\"\n}\n\n# Direct call\nJaSerializer.format(MySerializer, collection, conn, page: page)\n\n# In Phoenix Controller\nrender conn, \"index.json-api\", data: collection, opts: [page: page]\n```\n\n#### Builder\n\nYou can build the pagination links with\n`JaSerializer.Builder.PaginationLinks.build/2`\n\nSimply pass in the following:\n\n```elixir\nlinks =\n  JaSerializer.Builder.PaginationLinks.build(\n    %{\n      number: 2,\n      size: 10,\n      total: 20\n    },\n    conn\n  )\n```\n\nSee `JaSerializer.Builder.PaginationLinks` for how to customize.\n\n### Scrivener Integration\n\nIf you are using Scrivener for pagination, all you need to do is pass the\nresults of `paginate/2` to your serializer.\n\n```elixir\npage = MyRepo.paginate(MyModel, params.page)\n\n# Direct call\nJaSerializer.format(MySerializer, page, conn, [])\n\n# In Phoenix controller\nrender conn, \"index.json-api\", data: page\n```\n\nWhen integrating with Scrivener, the URLs generated will be based on the\n`Plug.Conn`'s path. This can be overridden by passing in the `page[:base_url]`\noption.\n\n```elixir\nrender conn, \"index.json-api\", data: page, opts: [base_url: \"http://example.com/foos\"]\n```\n\nYou can also configure `ja_serializer` to use a global default URL\nbase for all links.\n\n```elixir\nconfig :ja_serializer,\n  page_base_url: \"http://example.com:4000/v1/\"\n```\n\n*Note*: The resulting URLs will use the JSON-API recommended `page` query\nparam.\n\nExample URL:\n`http://example.com:4000/v1/posts?page[page]=2\u0026page[page-size]=50`\n\n### Meta Data\n\nJaSerializer allows adding top level meta information via the `meta` option. The `meta` option\nexpects to receive a `Map` containing the data which will be rendered under the top level meta key.\n\n```elixir\nmeta_data = %{\n  \"key\" =\u003e \"value\"\n}\n\n# Direct call\nJaSerializer.format(MySerializer, data, conn, meta: meta_data)\n\n# In Phoenix controller\nrender conn, \"index.json-api\", data: data, opts: [meta: meta_data]\n```\n\n## Customization\n\n### Key Format (for Attribute, Relationship and Query Param)\n\nBy default keys are `dash-erized` as per the JSON:API 1.0 recommendation, but keys can be customized via config.\n\nIn your `config.exs` file you can use `camel_cased` recommended by upcoming JSON:API 1.1:\n\n```elixir\nconfig :ja_serializer,\n  key_format: :camel_cased\n```\n\nOr `underscored`:\n\n```elixir\nconfig :ja_serializer,\n  key_format: :underscored\n```\n\nYou may also pass custom function for serialization and a second optional one for deserialization. Both accept a single binary argument:\n\n```elixir\ndefmodule MyStringModule do\n  def camelize(key), do: key #...\n  def underscore(key), do: key #...\nend\n\nconfig :ja_serializer,\n  key_format: {:custom, MyStringModule, :camelize, :underscore}\n```\n\n### Custom Attribute Value Formatters\n\nWhen serializing attribute values more complex than string, numbers, atoms or\nlist of those things it is recommended to implement a custom formatter.\n\nTo implement a custom formatter:\n\n```elixir\ndefimpl JaSerializer.Formatter, for: [MyStruct] do\n  def format(struct), do: struct\nend\n```\n\n### Pluralizing All Types By Default\n\nYou can opt-in to pluralizing all types for default:\n\n```elixir\nconfig :ja_serializer,\n  pluralize_types: true\n```\n\n\n## Complimentary Libraries\n\n* [JaResource](https://github.com/vt-elixir/ja_resource) - WIP behaviour for creating JSON-API controllers in Phoenix.\n* [voorhees](https://github.com/danmcclain/voorhees) - Testing tool for JSON API responses\n* [inquisitor](https://github.com/DockYard/inquisitor) - Composable query builder for Ecto\n* [scrivener](https://github.com/drewolson/scrivener) - Ecto pagination\n\n## License\n\nJaSerializer source code is released under Apache 2 License. Check LICENSE\nfile for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvt-elixir%2Fja_serializer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvt-elixir%2Fja_serializer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvt-elixir%2Fja_serializer/lists"}