Convert map with nested structures

Hi, please. I don’t know how to do something.

I have this map:

res = %{
  "course" => %{
    "course_target" => [                                                                                                                         
      %{
        "course_id" => 1,
        "id" => 1,
        "inserted_at" => "2021-07-23T02:11:32",
        "requirement_type" => "mandatory",
        "target" => "brand",
        "target_id" => "987",
        "updated_at" => "2021-07-23T02:11:32"
      },
      %{
        "course_id" => 1,
        "id" => 2,
        "inserted_at" => "2021-07-23T14:42:33",
        "requirement_type" => "mandatory",
        "target" => "brand",
        "target_id" => "234",
        "updated_at" => "2021-07-23T14:42:33"
      },
      %{
        "course_id" => 1,
        "id" => 3,
        "inserted_at" => "2021-07-23T18:55:16",
        "requirement_type" => "mandatory",
        "target" => "brands",
        "target_id" => "876",
        "updated_at" => "2021-07-23T18:55:16"
      }
    ],
    "id" => 1,
    "name" => "hello",
    "status" => "active"
  }
}

I need to convert this to this format %{id: 1, name: "hello"}

For that, I have this res |> Map.get("course") |> Map.new(fn {key, value} -> {String.to_atom(key), value} end) and this helps me convert this with the following structure:

%{
  course_target: [
    %{
      "course_id" => 1,
      "id" => 1,
      "inserted_at" => "2021-07-23T02:11:32",
      "requirement_type" => "mandatory",
      "target" => "brand",
      "target_id" => "987",
      "updated_at" => "2021-07-23T02:11:32"
    },
    %{
      "course_id" => 1,
      "id" => 2,
      "inserted_at" => "2021-07-23T14:42:33",
      "requirement_type" => "mandatory",
      "target" => "brand",
      "target_id" => "234",
      "updated_at" => "2021-07-23T14:42:33"
    },
    %{
      "course_id" => 1,
      "id" => 3,
      "inserted_at" => "2021-07-23T18:55:16",
      "requirement_type" => "mandatory",
      "target" => "brands",
      "target_id" => "876",
      "updated_at" => "2021-07-23T18:55:16"
    }
  ],  id: 1,
  name: "hello",
  provider_course_id: 432,
  provider_module_id: 543,
  status: "active"
}

And that’s OK. But if you watch deeply you’ll see that the course_target list was not converted into the format I need.

My question is:
Does anyone here know the way to have the whole map completely converted?

Thanks in advance

I don’t think it is a good idea… if the data comes from outside, it should be casted.
You might hit problems when casting outside string to atom.

defmodule Demo do
  def run(res), do: do_process(res)

  defp do_process(map) when is_map(map), do: Map.new(map, &do_process(&1))
  defp do_process(list) when is_list(list), do: Enum.map(list, &do_process(&1))
  defp do_process({k, v}) when is_map(v), do: {String.to_atom(k), do_process(v)}
  defp do_process({k, v}) when is_list(v), do: {String.to_atom(k), do_process(v)}
  defp do_process({k, v}), do: {String.to_atom(k), v}
end

and then…

iex> Demo.run res
%{
  course: %{
    course_target: [
      %{
        course_id: 1,
        id: 1,
        inserted_at: "2021-07-23T02:11:32",
        requirement_type: "mandatory",
        target: "brand",
        target_id: "987",
        updated_at: "2021-07-23T02:11:32"
      },
      %{
        course_id: 1,
        id: 2,
        inserted_at: "2021-07-23T14:42:33",
        requirement_type: "mandatory",
        target: "brand",
        target_id: "234",
        updated_at: "2021-07-23T14:42:33"
      },
      %{
        course_id: 1,
        id: 3,
        inserted_at: "2021-07-23T18:55:16",
        requirement_type: "mandatory",
        target: "brands",
        target_id: "876",
        updated_at: "2021-07-23T18:55:16"
      }
    ],
    id: 1,
    name: "hello",
    status: "active"
  }
}

You could have more safety if You check k is a binary, and is in a list of predefined set of attributes.

2 Likes