diff --git a/README.md b/README.md index 63ed349b..2fb34e4e 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ config :jsonapi, namespace: "/api", field_transformation: :underscore, remove_links: false, + serialize_nil_relationships: false, json_library: Jason, paginator: nil ``` @@ -219,6 +220,10 @@ config :jsonapi, `:camelize_shallow` or `:dasherize_shallow`. - **remove_links**. `links` data can optionally be removed from the payload via setting the configuration above to `true`. Defaults to `false`. +- **serialize_nil_relationships**. By default, relationships on a resource that + are `nil` will be omitted during serialization. Setting this to `true` will + serialize these relationships, provided they are loaded on the resource. + Defaults to `false`. - **json_library**. Defaults to [Jason](https://hex.pm/packages/jason). - **paginator**. Module implementing pagination links generation. Defaults to `nil`. diff --git a/lib/jsonapi/serializer.ex b/lib/jsonapi/serializer.ex index 353bdebe..d76be9ae 100644 --- a/lib/jsonapi/serializer.ex +++ b/lib/jsonapi/serializer.ex @@ -270,7 +270,7 @@ defmodule JSONAPI.Serializer do |> Enum.uniq() end - defp assoc_loaded?(nil), do: false + defp assoc_loaded?(nil), do: serialize_nil_relationships?() defp assoc_loaded?(%{__struct__: Ecto.Association.NotLoaded}), do: false defp assoc_loaded?(_association), do: true @@ -304,6 +304,9 @@ defmodule JSONAPI.Serializer do defp remove_links?, do: Application.get_env(:jsonapi, :remove_links, false) + @spec serialize_nil_relationships? :: boolean() + defp serialize_nil_relationships?, do: Application.get_env(:jsonapi, :serialize_nil_relationships, false) + defp transform_fields(fields) do case Utils.String.field_transformation() do :camelize -> Utils.String.expand_fields(fields, &Utils.String.camelize/1) diff --git a/test/jsonapi/serializer_test.exs b/test/jsonapi/serializer_test.exs index c3f28e0a..262bc0d2 100644 --- a/test/jsonapi/serializer_test.exs +++ b/test/jsonapi/serializer_test.exs @@ -202,6 +202,9 @@ defmodule JSONAPI.SerializerTest do %PolymorphicDataTwo{} -> "polymorphic_data_one" + + nil -> + nil end end @@ -221,6 +224,7 @@ defmodule JSONAPI.SerializerTest do on_exit(fn -> Application.delete_env(:jsonapi, :field_transformation) + Application.delete_env(:jsonapi, :serialize_nil_relationships) end) {:ok, []} @@ -360,7 +364,7 @@ defmodule JSONAPI.SerializerTest do assert Enum.count(encoded[:included]) == 1 end - test "serialize handles a nil relationship" do + test "serialize excludes a nil relationship by default" do data = %{ id: 1, text: "Hello", @@ -380,11 +384,96 @@ defmodule JSONAPI.SerializerTest do assert attributes[:body] == data[:body] assert encoded_data[:links][:self] == PostView.url_for(data, nil) + refute Map.has_key?(encoded_data[:relationships], :best_comments) + assert map_size(encoded_data[:relationships]) == 1 + + assert Enum.count(encoded[:included]) == 1 + end + + test "serialize excludes unloaded ecto association by default" do + data = %{ + id: 1, + text: "Hello", + body: "Hello world", + author: %{id: 2, username: "jason"}, + best_comments: %{__struct__: Ecto.Association.NotLoaded} + } + + encoded = Serializer.serialize(PostView, data, nil) + + encoded_data = encoded[:data] + assert encoded_data[:id] == PostView.id(data) + assert encoded_data[:type] == PostView.type() + + attributes = encoded_data[:attributes] + assert attributes[:text] == data[:text] + assert attributes[:body] == data[:body] + + assert encoded_data[:links][:self] == PostView.url_for(data, nil) + refute Map.has_key?(encoded_data[:relationships], :best_comments) assert map_size(encoded_data[:relationships]) == 1 assert Enum.count(encoded[:included]) == 1 end + test "serialize includes nil relationships when configured" do + Application.put_env(:jsonapi, :serialize_nil_relationships, true) + + data = %{ + id: 1, + text: "Hello", + body: "Hello world", + author: %{id: 2, username: "jason"}, + best_comments: nil + } + + encoded = Serializer.serialize(PostView, data, nil) + + encoded_data = encoded[:data] + assert encoded_data[:id] == PostView.id(data) + assert encoded_data[:type] == PostView.type() + + attributes = encoded_data[:attributes] + assert attributes[:text] == data[:text] + assert attributes[:body] == data[:body] + + assert encoded_data[:links][:self] == PostView.url_for(data, nil) + assert map_size(encoded_data[:relationships]) == 3 + assert encoded_data[:relationships][:best_comments][:data] == nil + assert encoded_data[:relationships][:polymorphics][:data] == nil + + assert Enum.count(encoded[:included]) == 1 + end + + test "serialize excludes unloaded ecto association when serialize nil relationships configured" do + Application.put_env(:jsonapi, :serialize_nil_relationships, true) + + data = %{ + id: 1, + text: "Hello", + body: "Hello world", + author: %{id: 2, username: "jason"}, + best_comments: %{__struct__: Ecto.Association.NotLoaded} + } + + encoded = Serializer.serialize(PostView, data, nil) + + encoded_data = encoded[:data] + assert encoded_data[:id] == PostView.id(data) + assert encoded_data[:type] == PostView.type() + + attributes = encoded_data[:attributes] + assert attributes[:text] == data[:text] + assert attributes[:body] == data[:body] + + assert encoded_data[:links][:self] == PostView.url_for(data, nil) + assert map_size(encoded_data[:relationships]) == 2 + assert Map.has_key?(encoded_data[:relationships], :polymorphics) + refute Map.has_key?(encoded_data[:relationships], :best_comments) + + assert Enum.count(encoded[:included]) == 1 + end + test "serialize handles a relationship self link on a show request" do data = %{ id: 1,