55

I'm trying to convert a struct to a map to be able to clean all the nil values

I'm currently using this code

  case Nadia.get_updates  do
    {:ok, results} ->
      Map.from_struct(results)
      |> Enum.filter(fn {_, v} -> v != nil end)
      |> Enum.into(%{})

Note: Nadia.get_updates returns the following structure: https://hexdocs.pm/nadia/Nadia.Model.Update.html#t:t/0

Yet I'm always receiving the following error: no function clause matching in Map.from_struct/1

Charles Okwuagwu
  • 10,538
  • 16
  • 87
  • 157
user2070502
  • 603
  • 1
  • 5
  • 8

4 Answers4

114

Since v0.15 we have Map.from_struct/1 which does exactly this.

defmodule User do
  defstruct [:name]
end

Map.from_struct(%User{name: "valim"})
#=> %{name: "valim"}
Juliano
  • 2,402
  • 1
  • 20
  • 22
15

There's another trick

my_struct = %MyStruct{}
my_map = Map.delete my_struct, :__struct__
radzserg
  • 1,258
  • 1
  • 13
  • 22
  • `mymap = Map.delete(mystruct, :__meta__) |> Map.delete(:__struct__)` – nelsonic Mar 31 '20 at 16:26
  • @nelsonic - I actually like the approach that Juliano suggested Map.from_struct(%User{name: "valim"}) is better way to do that – radzserg Apr 01 '20 at 10:04
  • Yeah, `Map.from_struct/1` is definitely good for regular structs. But when the struct is an Ecto changeset `Map.from_struct/1` won't fully work so we need: `mymap = Map.from_struct(changeset) |> Map.delete(:__meta__)` – nelsonic Apr 01 '20 at 12:34
2
get_updates([{atom, any}]) ::
  {:ok, [Nadia.Model.Update.t]} |
  {:error, Nadia.Model.Error.t}

If successful it returns a list of Nadia.Model.Update.t. That's why you get that error.

Sasha Fonseca
  • 2,257
  • 23
  • 41
1

FWIW, I wrote a library that will do this kind of thing for you automagically w/o having to pull the data structure apart.

https://github.com/philosophers-stone/transform

This is from the test code:

test "implement scrub of empty values from map" do
  data = %{ :a => nil, :b => "", :c => "a"}
  replace_empty = fn(string, _d) -> if( string == "", do: nil , else: string) end
  replace_nil = fn(map, _depth) ->  for {k, v} <- map, v != nil , into: %{}, do: {k, v} end
  potion = %{ BitString => replace_empty, Map => replace_nil}

  assert PhStTransform.transform(data, potion) == %{:c => "a"}

end

Note, if data was a list of maps this would still work just fine. The PhStTransform library will just keep pulling the data apart until it finds a map. In your case you would use a replace_nil function that worked on the kinds of structs you are interested in.