vt-elixir / ja_serializer

JSONAPI.org Serialization in Elixir.
Other
640 stars 148 forks source link

Sparse field param for function based attribute breaks #337

Open elvanja opened 3 years ago

elvanja commented 3 years ago

I have a serializer defined like this:

  attributes([
    :name,
    :custom_property
  ])

Where :name exists in the original struct, but :custom_property is calculated in same named function.

If I try to use sparse fields that include the custom property via e.g. fields[record]=custom-property, it breaks when trying to convert from dashed to underscored key value while building the attributes (error details below).

A fix for this is to just apply formatting while doing the conversion. E.g. in JaSerializer.Builder.Utils.safe_atom_list/1:

  def safe_atom_list(field_str) do
    field_str
    |> String.split(",")
    |> Enum.map(&JaSerializer.ParamParser.Utils.format_key/1) # <---- this is the fix
    |> Enum.map(&String.to_existing_atom/1)
  end

After that, it all seems to be working fine. I can turn this into a PR, but I am not sure if this is the right place to fix the issue.

And, here is the stack trace:

* (exit) an exception was raised:
    ** (ArgumentError) argument error
        :erlang.binary_to_existing_atom("custom-property", :utf8)
        (elixir 1.10.3) lib/string.ex:2303: String.to_existing_atom/1
        (elixir 1.10.3) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
        (elixir 1.10.3) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
        (ja_serializer 0.16.0) lib/ja_serializer/builder/attribute.ex:37: JaSerializer.Builder.Attribute.do_filter/2
        (ja_serializer 0.16.0) lib/ja_serializer/builder/attribute.ex:11: JaSerializer.Builder.Attribute.build/1
        (ja_serializer 0.16.0) lib/ja_serializer/builder/resource_object.ex:23: JaSerializer.Builder.ResourceObject.build/1
        (elixir 1.10.3) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
        (ja_serializer 0.16.0) lib/ja_serializer/builder/top_level.ex:34: JaSerializer.Builder.TopLevel.build/1
        (ja_serializer 0.16.0) lib/ja_serializer.ex:62: JaSerializer.format/4
        (phoenix 1.4.17) lib/phoenix/view.ex:410: Phoenix.View.render_to_iodata/3
elvanja commented 3 years ago

P.S. If it helps, I also have a workaround. In controller, I apply this transformation to the sparse fields params:

  defp underscore(nil), do: nil

  defp underscore(string) when is_binary(string), do: String.replace(string, "-", "_")

  defp underscore(%{} = map) do
    map
    |> Enum.map(fn {key, value} ->
      {key, underscore(value)}
    end)
    |> Enum.into(%{})
  end

Used like this:

    render(conn, "index.json",
      data: some_data,
      opts: [
        fields: underscore(params["fields"])
      ]
    )
beerlington commented 3 years ago

@elvanja sorry I haven't had a chance to look at this yet. It seems like this should affect any sparse field name that needs to get converted from dashes to underscore. If the regular (non-custom) property name has an underscore, does that break too?

elvanja commented 3 years ago

Hi! Sure, no problemo, it is not an emergency 😄 And yes, the non-custom properties also break, on main record or on relations.