Sure thing! Look that in original version I’m using map
as a root of input
, but in code you would find that also list
is supported. The proper info
for this data
would be:
data = %{
gps: %{
lat: 48.857144,
lon: 2.340242,
altitude: 35.0
},
macaddress: "B8:C0:78:CD:12:AB"
}
info = [%{gps: [:lat, :lon]}, :macaddress]
iex> Example.sample(data, info)
However I found one thing. In original code list
part is not best. I would rewrite it to tuple
instead, for example:
defmodule Example do
def sample(acc \\ %{}, data, info)
# in case nested value does not exists
def sample(acc, nil, _info), do: acc
# instead of above you may want to use another code
# as it would place a nil value for each nested info who does not exists in data you passed
#
# def sample(acc, nil, info) when is_atom(info), do: Map.put(acc, info, nil)
# def sample(acc, nil, info) when is_list(info), do: Enum.reduce(info, acc, &sample(&2, nil, &1))
#
# def sample(acc, nil, info) when is_map(info) do
# Enum.reduce(info, acc, &sample(&2, nil, elem(&1, 1)))
# end
# when we want to fetch nested fields and root fields at the same time
# we are passing {nested_fields_map_info, root_fields_list_info}
def sample(acc, data, {map_info, list_info}) when is_map(map_info) and is_list(list_info) do
acc |> sample(data, map_info) |> sample(data, list_info)
end
# when we need to fetch a flat list of fields
def sample(acc, data, info) when is_list(info) do
Enum.reduce(info, acc, &Map.put(&2, &1, data[&1]))
end
# here we are reducing info map over our acc
# which means all nested fields logic goes here
def sample(acc, data, info) when is_map(info) do
Enum.reduce(info, acc, fn {info_key, info_value}, acc ->
sample(acc, data, info_key, info_value)
end)
end
# this clause would match if we want to fetch just one nested field
# %{nested: :field}
defp sample(acc, data, info_key, info_value) when is_atom(info_value) do
Map.put(acc, info_value, get_in(data, [info_key, info_value]))
end
# in any other case call the same logic, but with nested data and info
# %{nested: [:field1, :field2, …]}
# or
# %{nested: %{nested_level2: …}}
# or
# %{nested: {%{nested_level2: …}, [:field]}
defp sample(acc, data, info_key, info_value), do: sample(acc, data[info_key], info_value)
end
data = %{
gps: %{
lat: 48.857144,
lon: 2.340242,
altitude: 35.0
},
metadata: %{
payload: %{
content: "1aff4c0002154a080bff4c0010774058308876aa3a29",
scantime: 1_644_362_438
},
macaddress: "B8:C0:78:CD:12:AB",
receivetime: 1_663_000_169,
tags: "v1.2,test.app"
}
}
info = %{gps: [:lat, :lon], metadata: {%{payload: :scantime}, [:receivetime]}}
iex> Example.sample(data, info)
data2 = %{
gps: %{
lat: 48.857144,
lon: 2.340242,
altitude: 35.0
},
macaddress: "B8:C0:78:CD:12:AB"
}
iex> Example.sample(data2, {%{gps: [:lat, :lon]}, [:macaddress]})
This way instead of [%{nested: …}, :field1, …]
we have {%{nested: …}, [:field1, …]}
which separates nested map
info from root list
info. This way we no longer need to call Enum.groiup_by/2
.