Packages
estructura
1.2.11
1.14.0
1.13.0
1.12.0
1.11.0
1.10.2
1.10.1
1.10.0
1.9.0
1.8.3
1.8.2
1.8.1
1.8.0
1.7.2
1.7.1
1.7.0
1.6.1
1.6.0
1.5.0
1.4.2
1.4.1
1.4.0
1.3.3
1.3.2
1.3.1
1.3.0
1.2.12
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.0
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.0
0.1.0
Extensions for Elixir structures.
Current section
Files
Jump to
Current section
Files
lib/estructura/flattenable.ex
defprotocol Estructura.Flattenable do
@doc """
The function returning the flattened input.
This protocol is explicitly handful when deeply nested structs are to be serialized.
```elixir
iex|%_{}|1 ▶ %Estructura.User{}
%Estructura.User{
address: %Estructura.User.Address{
city: nil,
street: %Estructura.User.Address.Street{house: nil, name: []}
},
data: %Estructura.User.Data{age: nil},
name: nil
}
iex|%_{}|2 ▶ Estructura.Flattenable.flatten(%Estructura.User{}, coupler: "-", except: ~w|address-street data-age|)
%{"address-city" => nil, "birthday" => nil, "created_at" => nil, "name" => nil}
iex|%_{}|3 ▶ Estructura.Flattenable.flatten(%Estructura.User{}, only: ~w|address_street data_age|)
%{"address_street_house" => nil, "data_age" => nil}
iex|%_{}|4 ▶ Estructura.Flattenable.flatten(%Estructura.User{}, only: ~w|address|)
%{"address_city" => nil, "address_street_house" => nil}
```
To enable it for your struct, use `@derive Estructura.Flattenable` or
`@derive {Estructura.Flattenable, options}`. `Estructura` implementations derive it be default.
"""
@spec flatten(t(), keyword()) :: term()
def flatten(input, options \\ [])
end
defimpl Estructura.Flattenable, for: Any do
defmacro __deriving__(module, _struct, options) do
quote do
defimpl Estructura.Flattenable, for: unquote(module) do
def flatten(input, options) do
options =
unquote(options)
|> Keyword.merge(options)
|> Keyword.update(:only, [], fn only -> Enum.map(only, &to_string/1) end)
|> Keyword.update(:except, [], fn except -> Enum.map(except, &to_string/1) end)
input
|> Map.from_struct()
|> Estructura.Flattenable.flatten(options)
end
end
end
end
def flatten(input, _options), do: input
end
defimpl Estructura.Flattenable, for: List do
def flatten(enum, options) do
enum
|> Keyword.keyword?()
|> if(do: enum, else: enum |> Enum.with_index() |> Enum.map(fn {v, idx} -> {idx, v} end))
|> Map.new()
|> Estructura.Flattenable.flatten(options)
end
end
defimpl Estructura.Flattenable, for: Map do
defp filter({key, _}, options), do: filter(key, options)
defp filter(key, options) do
coupler = Keyword.get(options, :coupler, "_")
only = Keyword.get(options, :only, [])
except = Keyword.get(options, :except, [])
(only == [] or key in only or Enum.any?(only, &String.starts_with?(key, &1 <> coupler))) and
not (key in except or Enum.any?(except, &String.starts_with?(key, &1 <> coupler)))
end
def flatten(map, options) do
coupler = Keyword.get(options, :coupler, "_")
outer_acc = Keyword.get(options, :__acc__, %{key: [], acc: %{}})
map
|> Enum.reduce(outer_acc, fn {k, v}, %{key: key, acc: acc} ->
acc =
if not is_nil(Estructura.Flattenable.impl_for(v)) and v != %{} and v != [] do
Estructura.Flattenable.flatten(
v,
Keyword.put(options, :__acc__, %{key: [k | key], acc: acc})
)
else
Map.put(acc, [k | key] |> Enum.reverse() |> Enum.join(coupler), v)
end
%{key: key, acc: acc}
end)
|> Map.fetch!(:acc)
|> Map.filter(&filter(&1, options))
end
end