Elixir maps from string association maps

λ November 15, 2019
Tags: elixir

Erlang and Elixir

..are pretty damned nice to work with but sometimes the interoperability isn’t always what you’d like through no fault of your own, existing services having been installed and configured not by you etc.

My example today is one of an Elixir application calling out to a back-end micro service to obtain some data, and the library it uses to do this was written with Erlang some time in the past.

The issue is that the structure that comes back from the RPC call looks something like this:

    %{
        "address" => :null,
        "branch" => :null,
        "categories" => [],
        "company" => "Company A",
        "currency" => "GBP",
        "icon" => %{"icon" => 0, "url" => :null},
        "id" => "3802ebae-52e6-4c69-84cb-451a55172445",
        "notes" => :null,
        "partner" => :null,
        "reference" => :null,
        "status" => %{"enabled" => true, "id" => 2, "name" => "ACTIVE"},
        "type" => "DEFAULT",
        "web" => :null
    }

and what you’d really like is this:

    %{
        address: nil,
        branch: nil,
        categories: [],
        company: "Company A",
        currency: "GBP",
        icon:  %{icon: 0, url: nil},
        id: "3802ebae-52e6-4c69-84cb-451a55172445",
        notes: => nil,
        partner: => nil,
        reference: => nil,
        status: => %{enabled: true, id: 2, name: "ACTIVE"},
        type: "DEFAULT",
        web: nil
    }

Why? Because it’s leaner and more Elixir like and follows the rules that allow maps to work nicely with each-other and many more.

How? I love the smell of recursion in the morning.

The Solution

I have a module called “utils.ex” where i tend to stick the useful little things that come up now and then. This code does the above, it consumes a map and produces a cleaned map, having replaced any atoms with the value :null to be ‘nil’, and detects if a value is itself a map and in need of conversion.

Here’s the code:

  @doc """
  Converts an Erlang JSON map into a more Elixir friendly one.

  The response was JSON from the Rabbit transport layer and uses
  string associations of the form:

  "key" => value

  here we attempt to clean up the map and return

  key: value

  Atoms of :null are mapped to nil
  Values that are %{} are also recursively processed
  """
  def reshape(a_list) when is_list(a_list) do
    Enum.into(a_list, [], fn item ->
      reshape(item)
      end)
  end

  def reshape(a_map) when is_map(a_map) do
    Enum.into(a_map, %{}, fn {k, v} ->
      {atom_key(k), reshape(v)} end)
  end

  def reshape(:null), do: nil

  def reshape(thing), do: thing

  defp atom_key(x) do
    cond do
      is_binary(x) -> String.to_atom(x)
      true -> x
    end
  end

Stick that somewhere useful. I am off back to consider making this a core part of the transport layer…

Comments