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

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
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
Phillipp
Hey, I have a NanoPi-M3 and try to install Elixir on their Ubuntu image. I followed the Raspberry Pi installation instructions from the ...
New
makeitrein
Hey all, just started picking up Elixir last week and am writing a scraper as a learning project. Baby step #1 is extracting the number ...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
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
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New

Other popular topics Top

senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
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
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement