diff --git a/README.md b/README.md index 43d758b1..971ca005 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,17 @@ end In case both `:only` and `:except` keys are defined, the `:except` option is ignored. +It is also possible to exclude all attributes with a specific value. This can be useful in the case where `Null` might mean something to an API and you don't want to send the attribute at all. + +```elixir +defmodule SparsePerson do + @derive {Poison.Encoder, redact: :empty} + defstruct name: :empty, age: :empty +end +``` + +The `:redact` option can be included with `:only` and `:except` + ### Key Validation According to [RFC 7159][4] keys in a JSON object should be unique. This is diff --git a/lib/poison/encoder.ex b/lib/poison/encoder.ex index 6fec203d..cb0826bb 100644 --- a/lib/poison/encoder.ex +++ b/lib/poison/encoder.ex @@ -379,18 +379,26 @@ defimpl Poison.Encoder, for: Any do def deriving(module, _struct, options) do only = options[:only] except = options[:except] + redact = options[:redact] extractor = cond do only -> - quote(do: Map.take(struct, unquote(only))) + quote( + do: json_redact(Map.take(struct, unquote(only)), unquote(redact)) + ) except -> except = [:__struct__ | except] - quote(do: Map.drop(struct, unquote(except))) + + quote( + do: json_redact(Map.drop(struct, unquote(except)), unquote(redact)) + ) true -> - quote(do: :maps.remove(:__struct__, struct)) + quote( + do: json_redact(:maps.remove(:__struct__, struct), unquote(redact)) + ) end quote do @@ -398,6 +406,20 @@ defimpl Poison.Encoder, for: Any do def encode(struct, options) do Encoder.Map.encode(unquote(extractor), options) end + + def json_redact(struct, nil), do: struct + + def json_redact(struct, target) do + Enum.reduce(struct, %{}, fn {key, value}, redacted -> + case value do + ^target -> + redacted + + value -> + Map.put(redacted, key, value) + end + end) + end end end end diff --git a/test/poison/encoder_test.exs b/test/poison/encoder_test.exs index 4dd85c03..d64516c6 100644 --- a/test/poison/encoder_test.exs +++ b/test/poison/encoder_test.exs @@ -196,6 +196,21 @@ defmodule Poison.EncoderTest do defstruct name: "", size: 0 end + defmodule DerivedUsingRedact do + @derive {Poison.Encoder, redact: :empty} + defstruct name: :empty, size: 0 + end + + defmodule DerivedUsingOnlyAndRedact do + @derive {Poison.Encoder, only: [:name, :size], redact: :empty} + defstruct name: "", size: :empty, shape: "tirangle" + end + + defmodule DerivedUsingExceptAndRedact do + @derive {Poison.Encoder, except: [:name], redact: :empty} + defstruct name: "", size: 10, shape: :empty + end + defmodule NonDerived do defstruct name: "" end @@ -221,6 +236,33 @@ defmodule Poison.EncoderTest do } assert Poison.decode!(to_json(derived_using_except)) == %{"size" => 10} + + derived_using_redact = %DerivedUsingRedact{ + name: :empty, + size: 10 + } + + assert Poison.decode!(to_json(derived_using_redact)) == %{"size" => 10} + + derived_using_only_and_redact = %DerivedUsingOnlyAndRedact{ + name: "test", + size: :empty, + shape: "tirangle" + } + + assert Poison.decode!(to_json(derived_using_only_and_redact)) == %{ + "name" => "test" + } + + derived_using_except_and_redact = %DerivedUsingExceptAndRedact{ + name: "test", + size: 10, + shape: :empty + } + + assert Poison.decode!(to_json(derived_using_except_and_redact)) == %{ + "size" => 10 + } end test "EncodeError" do