Utilities for traversing nested maps?

I’m trying to write something that pretty-prints nested maps returned by MapDiff. I’ve already looked into Scribe and it doesn’t do quite what I want. I’m looking for something that will print only leaf nodes of a nested map.

If there is no such module for printing only leaf nodes of a nested map, then is there a module for traversing nested maps that I could use to create the pretty-printer myself? I’m considering Macro’s prewalk/postwalk functions for this but that kind of seems like overkill since it involves converting everything to an AST first…

I don’t know any built in module but it is very easy to implement yourself and customize what you need.

For instance, a very basic implementation could be this:

defmodule Mod do
  def collect(map) when is_map(map) do
    collect(map, [], [])
  end

  defp collect(map, prefix, acc) when is_map(map) do
    Enum.reduce(map, acc, fn {k, v}, acc ->
      collect(v, [k | prefix], acc)
    end)
  end

  defp collect(value, prefix, acc) do
    [{:lists.reverse(prefix), value} | acc]
  end

  def print_map_leafs(map) do
    map
    |> collect()
    |> Enum.each(&print_leaf/1)
  end

  defp print_leaf({path, v}) do
    IO.puts([Enum.map_join(path, ".", &to_string/1), ": ", inspect(v)])
  end
end

Mod.print_map_leafs(%{a: 1, b: %{"c" => 2, d: %{4 => 5}}})

The next step would be to change that code to add a traverse function that accepts a callback. The callback could accept three arguments: key, value, and prefix. Then re-implement print_map_leafs using traverse.

2 Likes

Many thanks!

You could also use the excellent Pathex library, the doc even has an example of traversing leaves

2 Likes

I was faced with a similar problem. A deeply nested structure of tuples, maps and lists. I waned to see if there was a match for a specific term/pattern in the nested mess. I did something like this:

defmodule ItemMember do

   def member?(item, item), do:
     true

   def member?(search_object, item) when is_tuple(search_object), do:
     member?(Tuple.to_list(search_object), item)

   def member?(search_object, item) when is_map_key(search_object, :__struct__), do:
      if search_object.__struct__ == MapSet,
         do: do_member?(MapSet.to_list(search_object), item),
         else: do_member?(Map.from_struct(search_object) |> Map.values(), item)

   def member?(search_object, item) when is_map(search_object), do:
      do_member?(Map.values(search_object), item)

   def member?(search_object, item) when is_list(search_object), do:
      do_member?(search_object, item)

   def member?(_, _), do:
      false

   defp do_member?([], _item), do:
      false

   defp do_member?([item | _rest], item), do:
      true

   defp do_member?([object | rest], item), do:
      member?(object, item) || do_member?(rest, item)

end
``
1 Like