egze

egze

Recursively traverse and update nested map

I have the following data structure:

data = %{
  "foo" => "bar",
  "abc" => %{
    "source" => "my-id",
    "value" => "12345"
  },
  "list" => [
    %{"source" => "my-id", "value" => "56789"},
    %{"source" => "my-other-id", "value" => "hello"}
  ]
}

I want to replace the inner maps that match the supplied source_id and replace them with a default value of %{"source" => "", "value" => ""}
If the matched map was is a list, then it should be removed (if it‘s a top level item in that list).

So if I call cleanup(data, "my-id"), the expected output should be:

%{
  "foo" => "bar",
  "abc" => %{
    "source" => "",
    "value" => ""
  },
  "list" => [
    %{"source" => "my-other-id", "value" => "hello"}
  ]
}

Should support any level of nesting. Having a bit of a brain freeze how to do it in an elegant way.

Please help with any pointers.

Most Liked

akash-akya

akash-akya

Not sure if this is elegant, but this one way of doing it

defmodule Example do
  def cleanup(data, source) when is_map(data) do
    if filter?(data, source) do
      %{"source" => "", "value" => ""}
    else
      Map.new(data, fn {key, value} ->
        {key, cleanup(value, source)}
      end)
    end
  end

  def cleanup([hd | tail], source) do
    list = if filter?(hd, source), do: tail, else: [hd | tail]
    Enum.map(list, &cleanup(&1, source))
  end

  def cleanup(data, _source), do: data

  defp filter?(data, source), do: match?(%{"source" => ^source}, data)
end
al2o3cr

al2o3cr

This is a fairly elegant solution, IMO:

defmodule Cleanup do
  def cleanup(data, source_id) do
    case data do
      %{"source" => ^source_id} ->
        %{"source" => "", "value" => ""}

      %{} ->
        Map.new(data, fn {k, v} -> {k, cleanup(v, source_id)} end)

      [_ | _] ->
        data
        |> Enum.reject(&match?(%{"source" => ^source_id}, &1))
        |> Enum.map(fn v -> cleanup(v, source_id) end)

      _ ->
        data
    end
  end
end

Just like the data structure, the code is recursive (for handling list elements and child maps).

The most unique part of this is the middle line in the list case:

Enum.reject(data, &match?(%{"source" => ^source_id}, &1))

Without that line, cleanup simply replaces every map with `“source” => ^source_id" with the empty version

Sebb

Sebb

Is it possible to change the data structure?

Some points that would make me think twice are

  • arbitrary keys (Most of the time I’d prefer %{key: "foo", value: "bar"} over %{foo: "bar"})
  • maps may occur in lists and as single item (why not lists of one instead?)
  • nested structure, most of the times a flat structure is easier to handle

Its impossible to say if that would be better not knowing what you are doing. But I found that thinking twice about the data structures can lead to way simpler code.

Last Post!

egze

egze

@akash-akya @al2o3cr Very cool. I see some similar ideas, will try it out. Thanks you both. I was also thinking about giving Pathex a try, but your solutions are much easier to understand.

@Sebb Unfortunately the data shape is out of my control. It’s an arbitrary external JSON without any standard shape, that represents params that need to be passed to another endpoint.

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New

Other popular topics Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 31586 112
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44265 214
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement