Elixir, iterate over nested map

2.8k Views Asked by At

I am new to elixir and I am trying to get some text from a very nested map.

So I am doing a get request to this link and I am decoding it with Jason.decode.

What I want to do is iterate over it and get every text value (sections->0->content->0->text).

In the end I just want it to be a big string of all the text values

(the link can always change so there might be more maps etc)

Thanks in advance!

3

There are 3 best solutions below

0
On

You can use Enum with the pipe operator |> to enumerate the data structure and extract the parts you want.

For example:

def pokedex(id) do
  HTTPoison.get!("https://pokemon.fandom.com/api/v1/Articles/AsSimpleJson?id=#{id}")
  |> Map.get(:body)
  |> Jason.decode!()
  |> Map.get("sections")
  |> Enum.reject(fn %{"content" => content} -> content === [] end)
  |> Enum.flat_map(fn %{"content" => content} ->
    content
    |> Enum.filter(fn %{"type" => type} -> type in ["text", "paragraph"] end)
    |> Enum.map(fn %{"text" => text} -> text end)
  end)
  |> Enum.join("\n")
end

A breakdown:

  • Map.get("sections") selects the contents of the sections.
  • Enum.reject(...) ignores empty sections.
  • Enum.flat_map iterates over the sections, gets the contents of each section, transforms it using the inner function, and then flatten the result into a single list.
    • Enum.filter(...) only process contents whose type property is text or paragraph.
    • Enum.map extracts the text property from each contents map.
  • Enum.join joins the resulting list of strings with a newline character.
0
On

Elixir provides(through erlang) some functions which can reflect upon the data-structures to check their type like is_map/1, is_list/1, is_number/1, is_boolean/1, is_binary/1, is_nil/1 etc. From docs

Try to go the common data-types you will have in your response. They could be a primitive, map or list of primitives where primitives are like boolean, numeric or a string.

Write a function which tries to recursively go through the data-structure you get until it reaches a primitive and then return the stringifed primitive

Ex. for maps, go through every value(ignore key) and if the value is not a primitive, call the function recursively with that node until you reach a primitive and can return a stringified value. Similar for lists

Something like this should work:

defmodule Test do
  def stringify(data) do
    cond do
      # <> is concatenation operator
      # -- call stringify() for every value in map and return concatenated string
      is_map(data) -> data |> Map.values |> Enum.reduce("", fn x, acc -> acc <> stringify(x) end)
      # -- call stringify() for every element in list and return concatenated string
      is_list(data) -> data |> Enum.reduce("", fn x, acc -> acc <> stringify(x) end)
      is_boolean(data) -> to_string(data)
      is_number(data) -> to_string(data)
      is_binary(data) -> data # elixir strings are binaries
      true -> ""
    end
  end
end

# For testing...
{:ok, %HTTPoison.Response{body: sbody}} = HTTPoison.get("https://pokemon.fandom.com/api/v1/Articles/AsSimpleJson?id=2409")
{:ok, body} = Poison.decode(sbody)
Test.stringify(body)

# returns
"Cyndaquil (Japanese: ヒノアラシ Hinoarashi) is the Fire-type Starter..."
0
On

If using the external package for this task is an option, you might give a try to the package I wrote for the exactly this purpose: Iteraptor.

It provides iteration/map/reduce functionality over nested Elixir enumerable terms.