sashaafm

sashaafm

What's the best way to access and retrieve data from deeply nested Maps and Lists?

So I’ve been working on some projects that envolve external services/API’s that provide data in XML and JSON. In some of these services I frequently incur into a problem which is deeply nested Maps and Lists. Since in some of these replies I know for a fact they will stay the same and won’t change over time or have any “randomness” to them I can simply pattern match them or use some Map.get/2 and Enum.fetch!/2 and get the job done.

However, when the reply may change (maybe the order of the elements or how deeply nested they are) I still haven’t found a proper way to access that data in a good, safe and idiomatic fashion.

I’ve read this blog post by José Valim but it didn’t help this situation in particular.

Here’s an example of the type of problem I’m facing:

         "name" => "External NAT", "natIP" => "146.148.23.208",
         "type" => "ONE_TO_ONE_NAT"}]

Let’s say I want to access the "natIp" key. Without having to use many Map.get/2 and List.first/1 or Enum.fetch!/2 how can we access the data? Specially when you’re not sure if this data structures will always come in the same order or with the same size from an external service (meaning I’m not sure if there is an actual way to pattern match it)?

Most Liked

gregvaughn

gregvaughn

This may not be a complete solution for you, but it sounds like it improves it at least one step of abstraction. Kernel.get_in/2 will access deeply nested maps very cleanly. The docs discuss details of how to use a function as a key, which would be necessary when you come to lists to find a matching map within it.

bbense

bbense

There is no one “right” answer to this question. There will always be a tradeoff between a generic tree search and a search that uses specific knowledge of the data structure.

One approach that I have been playing with is to turn this problem on it’s head and instead of
extracting the data out of the structure and into a function, you approach the problem by taking the function to the data.

I’ve written a general purpose library for dealing with deep data structures like this, it’s
phst_transform and it’s in hex.pm

It builds a map of functions that apply to specific data structure types and uses protocols underneath to do a depth first span of the entire data structure as a tree. One idea I’ve been playing with for extracting single data items from a deep tree like this would be to simply have a transform
that sent the item as a message to another process. Something like this.

potion = %{ Map => fn m → val = Map.get(m, “natIP”)
if (val , do: send pid, val )
m end }
PhStTranform.transform(data, potion )

It’s far from the most efficient way to get the value, but it does have the advantage of working with ANY data structure. I’m not sure PhStTranform is the last word in this kind of thinking, but I think there are a lot of possibilities in stepping back from the model of extract, manipulate and rebuild. If we start thinking about transforming the entire data structure or bringing the function to the data, many things that seem dauntingly complex become quite straightforward.

This kind of solution won’t work for every problem, but there is a lot you can do without actually embedding the knowledge of your entire data structure into your code. If you just know “somewhere in this blob is the Struct I care about”, you can just write the function for that struct.

gregvaughn

gregvaughn

Here’s a gist with an example of how to use Kernel.get_in/2 with function keys to navigate the nested sample data. It’s probably not a complete solution, but might be a step in the right direction.

kujua

kujua

Author of Erlang and Elixir for Imperative Programmers

This is an interesting problem. It won’t be possible to have a completely generic solution, but let us assume that you always get a list of maps the the following code would help:

defmodule NestedMaps do

  def nested_map() do
    %{"accessConfigs" =>
      [%{"kind" => "compute#accessConfig",
         "name" => "External NAT",
         "natIP" => "146.148.23.208",
         "type" => "ONE_TO_ONE_NAT"}
      ]
     }
  end

  def nested_map2() do
    %{"accessConfigs" =>
      [
        [%{"kind" => "compute#accessConfig",
         "name" => "External NAT",
         "natIP" => ["146.148.23.208","127.0.0.1"],
         "type" => "ONE_TO_ONE_NAT"}
        ],
        [{:config1,"c"}]
      ]
     }
  end

  def get_nested_map(nm) do
    %{"accessConfigs" => nestedmaplist} = nm
    nestedmaplist
  end

  def get_nested_map_from_list(nm, nestedlevel) when nestedlevel < 1 do
    nm
  end

  def get_nested_map_from_list(nm, nestedlevel) do
    get_nested_map_from_list(List.first(nm),nestedlevel-1)
  end

  def get_nested_map_value(nm, val) do
    Map.get nm,val
  end

end

A line like

NestedMaps.nested_map
    |> NestedMaps.get_nested_map 
    |> NestedMaps.get_nested_map_from_list(1) 
    |> NestedMaps.get_nested_map_value "natIP"

would return “146.148.23.208” from your original map list.

NestedMaps.nested_map2 
    |> NestedMaps.get_nested_map 
    |> NestedMaps.get_nested_map_from_list(2) 
    |> NestedMaps.get_nested_map_value "natIP"

would return [“146.148.23.208”, “127.0.0.1”] from the example in the code.

kujua

kujua

Author of Erlang and Elixir for Imperative Programmers

Just a quick refactoring.
To make get_nested_map_from_list tail recursive, it should look like this:

def get_nested_map_from_list(nm, nestedlevel) do
    l = List.first(nm)
    get_nested_map_from_list(l,nestedlevel-1)
end

Where Next?

Popular in Questions Top

Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
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
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement