michalmuskala / jason

A blazing fast JSON parser and generator in pure Elixir.
Other
1.6k stars 170 forks source link

Proposal: passing a function to @derive macro opts #117

Closed GPrimola closed 3 years ago

GPrimola commented 3 years ago

I'd like to propose the following option to __deriving__ macro, so we could do something like that:

@derive {Jason.Encoder, filter: &without_associations_not_loaded/1}

def without_associations_not_loaded(struct) do
  struct
    |> Map.from_struct()
    |> Enum.filter(fn
       {field, value} when is_map(value) ->
         value = Map.from_struct(value)

          not match?(%Ecto.Association.NotLoaded{}, value)
        _ -> true
      end)
end

WDYT?

michalmuskala commented 3 years ago

I don't think we'll be able to use funs in attributes like that. I believe this is not a limitation of Jason, but of attributes in Elixir in general.

In particular the issue is that the attribute is set before functions are compiled, so by the time you're setting it the function is not yet available.

GPrimola commented 3 years ago

@michalmuskala got it! Do you think that anonymous function could do the trick?

michalmuskala commented 3 years ago

I gave it a bit more thought, and TBH I'm not sure I see an advantage of that over writing the full implementation for the protocol manually. Leveraging Jason.Encode.keyword/2 function, this could look like:

defimpl Jason.Encoder, for: MyStruct do
  def encode(value, opts) do
    value
    |> Map.from_struct()
    |> Enum.reject(&match({_, %Ecto.Association.NotLoaded{}}, &1))
    |> Jason.Encode.keyword(opts)
  end
end
GPrimola commented 3 years ago

@michalmuskala thank you very much! That's a much better solution, of course, since Jason.Encoder is a protocol!

thanks again! :)

michalmuskala commented 3 years ago

I'm going to close this now. Let me know if there's anything else 🙂