Closed digitalcora closed 5 years ago
It's actually stated in code comments:
If the map is a struct with no
Enumerable
implementation, the struct is considered to be a single value.
@grantovich I, too, had the same realization as you did, and I'm replying with the hopes that perhaps this helps someone else that stumbles upon this useful library but is at a loss momentarily for how to leverage it.
I agree with your assessment that encoding, then decoding so that you can perform the casing transformations, and then encoding again is something that you'd like to avoid for every request if you could.
Trying to avoid that pattern lead me down this approach, though note I'm using Jason
rather than Poison
, but I think the implementation would be similar.
In my structs, I've defined a an implementation of Jason.Encoder
for my given struct, and inside that encode
function, I can now do the work that Jason
would have done for me for free - that is taking only the keys that I want - and then do what is necessary to be able to leverage ProperCase.to_camel_case
before finally doing the encoding via Jason
.
I've only played around with it a bit, but the rough idea is
defmodule MyApp.MyStruct do
use Ecto.Schema
import Ecto.Changeset
alias MyApp.Common
defimpl Jason.Encoder, for: [MyApp.MyStruct] do
def encode(struct, opts) do
map =
struct
|> Map.take([:key_1, :key_2])
|> Common.map_from_struct()
|> ProperCase.to_camel_case()
Jason.Encode.map(map, opts)
end
end
schema "my_structs" do
field :key_1, :integer
field :key_2, :integer
timestamps()
end
end
The call to Common.map_from_struct/1
would take some term and convert all structs in it to maps, no matter the level of nesting, so there's no issue with calling ProperCase.to_camel_case
later because all structs will be their map representations.
Though you have to write a bit more code, you have as much control as you want over the encoding behavior, and can perform the casing transformations without having to write your own library to do it.
Oops, I think I should have closed this a while ago, as we realized the way we were doing JSON rendering did not (did no longer?) align with Phoenix best practices. Specifically, I asserted that
data being encoded from Phoenix is a struct (using
@derive Poison.Encoder
or similar)
...was a "typical case", which I'm not sure is accurate. The current Phoenix guides, at least, recommend using view modules in a way that results in plain maps being passed to the encoder, which of course works fine with the ProperCase.JSONEncoder
. I would recommend anyone finding this issue in the future to consider this approach, since it also results in much better separation of concerns.
I'll close this now since, for me, it is not an issue. If anyone is in a situation that prevents using view modules for whatever reason, the workarounds documented above should help.
In the typical case where data being encoded from Phoenix is a struct (using
@derive Poison.Encoder
or similar),ProperCase.JSONEncoder.CamelCase
does nothing. This doesn't seem well-documented in the README, and makes this encoder not particularly useful out of the box, although it does make sense: the struct-ness cannot be preserved if the keys are changed, but it must be preserved in order to be encoded as desired by Poison/Jason/etc.To work around this, we're using a custom format encoder that encodes the struct, decodes it back into a map (now with filtered/stringified keys), runs that through
ProperCase
, and re-encodes it. This would probably be inefficient for large JSON responses, but it's acceptable for our application.I'm not sure off-hand how this issue could be addressed within
ProperCase
— there is nowhere in e.g. Poison to insert a "key transform" (although see https://github.com/devinus/poison/issues/44). I think this library could at least be explicit in the README thatJSONEncoder.CamelCase
only works in the unusual case where the data being encoded is plain maps all the way down, with no structs.