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

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
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
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
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
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New

We're in Beta

About us Mission Statement