From 324e09ac7e40ea14a48309c5dd29eaa7b988ffd2 Mon Sep 17 00:00:00 2001 From: Dan Janowski Date: Fri, 23 Feb 2018 21:41:00 -0500 Subject: [PATCH] Add Remap Protocol Intended for making key adjustments post Parse prior to Decode. Base case for camelCase to under_score. --- lib/poison/decoder.ex | 30 +++++++++++++++++++++++++++++- test/poison/decoder_test.exs | 17 +++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/poison/decoder.ex b/lib/poison/decoder.ex index d630b756..230d07f4 100644 --- a/lib/poison/decoder.ex +++ b/lib/poison/decoder.ex @@ -31,7 +31,8 @@ defmodule Poison.Decode do defp transform(nil, _keys, _as, _options), do: nil defp transform(value, keys, %{__struct__: _} = as, options) do - transform_struct(value, keys, as, options) + Decoder.Remap.remap( as, value, keys ) + |> transform_struct(keys, as, options) end defp transform(value, keys, as, options) when is_map(as) do @@ -123,3 +124,30 @@ defimpl Poison.Decoder, for: Any do value end end + +defprotocol Poison.Decoder.Remap do + @moduledoc """ + Hook via protocol for adjusting keys or the value map in general + when decoding to a struct. remap gets called after parsing but before + decoding to an `as` target. Base use case is translating camelCase + keys to under_score. + + ``` + defimpl Poison.Decoder.Remap, for: Contact2 do + def remap( _as, contact, keys ) when keys in [ :atoms, :atoms! ], do: contact + def remap( _as, contact, _keys ) do + Map.new( contact, fn { k, v } -> { Macro.underscore( k ), v } end ) + end + end + ``` + """ + + @fallback_to_any true + @type keys :: :atoms | :atoms! + @spec remap( as :: struct, value :: map, keys :: keys ) :: map + def remap( as, value, keys ) +end + +defimpl Poison.Decoder.Remap, for: Any do + def remap( _as, value, _keys ), do: value +end diff --git a/test/poison/decoder_test.exs b/test/poison/decoder_test.exs index 6c839a19..973917fd 100644 --- a/test/poison/decoder_test.exs +++ b/test/poison/decoder_test.exs @@ -29,6 +29,13 @@ defmodule Poison.DecoderTest do end end + defimpl Poison.Decoder.Remap, for: Contact2 do + def remap( _as, contact, keys ) when keys in [ :atoms, :atoms! ], do: contact + def remap( _as, contact, _keys ) do + Map.new( contact, fn { k, v } -> { Macro.underscore( k ), v } end ) + end + end + test "decoding single :as with string keys" do person = %{"name" => "Devin Torres", "age" => 27} expected = %Person{name: "Devin Torres", age: 27} @@ -197,4 +204,14 @@ defmodule Poison.DecoderTest do assert transform(address, %{as: %Address{}}) == "1 Main St., Austin, TX 78701" end + + test "decoding using remapping with camel case string keys" do + contact2 = %{ "callCount" => 7, "email" => "abc@123" } + assert transform(contact2, %{as: %Contact2{}}) == %Poison.DecoderTest.Contact2{ call_count: 7, email: "abc@123" } + end + + test "decoding using remapping with camel case atom keys" do + contact2 = %{ :callCount => 7, :email => "abc@123" } + assert transform(contact2, %{as: %Contact2{}, keys: :atoms}) == %Poison.DecoderTest.Contact2{ call_count: 0, email: "abc@123" } + end end