jsonelixirphoenix-frameworkelixir-poison

Elixir: find by value prefix in nested JSON


I'm trying to find URLs in a nested JSON response and map them. My function so far looks like this:

def list(env, id) do
  Service.get_document(env, id)
  |> Poison.decode!
  |> Enum.find(fn {_key, val} -> String.starts_with?(val, 'https') end)
end

The JSON looks roughly like this:

  "stacks": [
    {
      "boxes": [
        {
          "content": "https://ddd.cloudfront.net/photos/uploaded_images/000/001/610/original/1449447147677.jpg?1505956120",
          "box": "photo"
        }
      ]
    }
  ],
  "logo": "https://ddd.cloudfront.net/users/cmyk_banners/000/000/002/original/banner_CMYK.jpg?1397201875"

So URLs can have any key, and be at any level.

With that code I get this error:

no function clause matching in String.starts_with?/2

Anyone got a better way to find in JSON responses?


Solution

  • You'll have to use recursive function for this, which handles three types of data:

    1. For map, it recurses over all its values.
    2. For list, it recurses over all its elements.
    3. For string, it selects strings that start with "https"

    Here's a simple implementation which accepts a term and a string to check with starts_with?:

    defmodule A do
      def recursive_starts_with(thing, start, acc \\ [])
    
      def recursive_starts_with(binary, start, acc) when is_binary(binary) do
        if String.starts_with?(binary, start) do
          [binary | acc]
        else
          acc
        end
      end
      def recursive_starts_with(map, start, acc) when is_map(map) do
        Enum.reduce(map, acc, fn {_, v}, acc -> A.recursive_starts_with(v, start, acc) end)
      end
      def recursive_starts_with(list, start, acc) when is_list(list) do
        Enum.reduce(list, acc, fn v, acc -> A.recursive_starts_with(v, start, acc) end)
      end
    end
    
    data = %{
      "stacks" => [
        %{
          "boxes" => [
            %{
              "content" => "https://ddd.cloudfront.net/photos/uploaded_images/000/001/610/original/1449447147677.jpg?1505956120",
              "box" => "photo"
            }
          ]
        }
      ],
      "logo" => "https://ddd.cloudfront.net/users/cmyk_banners/000/000/002/original/banner_CMYK.jpg?1397201875"
    }
    
    data |> A.recursive_starts_with("https") |> IO.inspect
    

    Output:

    ["https://ddd.cloudfront.net/photos/uploaded_images/000/001/610/original/1449447147677.jpg?1505956120",
     "https://ddd.cloudfront.net/users/cmyk_banners/000/000/002/original/banner_CMYK.jpg?1397201875"]