-1

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!

J tunny
  • 1
  • 1
  • 1
  • Take a look here https://elixirforum.com/t/pattern-matching-encoded-json/2950 and then https://github.com/devinus/poison – GavinBrelstaff Mar 09 '19 at 17:05
  • @GavinBrelstaff Sorry, I don't really understand how to use the solutions you sent from elixirforum. Could you give me an example similar to my situation? – J tunny Mar 09 '19 at 17:51
  • I didn't give you an answer I gave you a comment. Your question is not trivial in Elixir because the json structure nor its depth are predictable - thus you need to figure a way of traversing it down to each text node and while collecting each of their content. In the real world one might flattened the whole structure and then pick out the text nodes -- https://elixirforum.com/t/how-to-flatten-a-map/12639/5#post_6 – GavinBrelstaff Mar 09 '19 at 18:49
  • The link is dead and I don't see any context, code attempt or data structure in the question itself. – ggorlen Jan 25 '22 at 21:28

3 Answers3

2

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.
Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
1

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..."
S.B
  • 827
  • 6
  • 7
0

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.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160