如何将地图键从字符串转换为Elixir中的原子

时间:2015-08-13 13:49:59

标签: elixir

在Elixir中将%{"foo" => "bar"}转换为%{foo: "bar"}的方式是什么?

14 个答案:

答案 0 :(得分:76)

使用Comprehensions

iex(1)> string_key_map = %{"foo" => "bar", "hello" => "world"}
%{"foo" => "bar", "hello" => "world"}

iex(2)> for {key, val} <- string_key_map, into: %{}, do: {String.to_atom(key), val}
%{foo: "bar", hello: "world"}

答案 1 :(得分:40)

我认为最简单的方法是使用Map.new

%{"a" => 1, "b" => 2} 
|> Map.new(fn {k, v} -> {String.to_atom(k), v} end)      

=> %{a: 1, b: 2}

答案 2 :(得分:22)

您可以使用Enum.reduce/3String.to_atom/1

的组合
%{"foo" => "bar"}
|> Enum.reduce(%{}, fn ({key, val}, acc) -> Map.put(acc, String.to_atom(key), val) end)

但是你应该警惕基于用户输入转换为原子,因为它们不会被垃圾收集,这可能导致内存泄漏。请参阅this issue

如果原子已存在,您可以使用String.to_existing_atom/1来防止这种情况。

答案 3 :(得分:7)

要构建@ emaillenin的答案,您可以检查键是否已经是原子,以避免String.to_atom获取的ArgumentError获取的密钥已经是原子。

for {key, val} <- string_key_map, into: %{} do
  cond do
    is_atom(key) -> {key, val}
    true -> {String.to_atom(key), val}
  end
end

答案 4 :(得分:6)

有一个图书馆,https://hex.pm/packages/morphix。它还具有嵌入式密钥的递归功能。

大部分工作都是在这个功能中完成的:

defp atomog (map) do
    atomkeys = fn({k, v}, acc) ->
      Map.put_new(acc, atomize_binary(k), v)
    end
    Enum.reduce(map, %{}, atomkeys)
  end

  defp atomize_binary(value) do 
    if is_binary(value), do: String.to_atom(value), else: value
  end

递归调用。在阅读@ Galzer的回答后,我很快就会将其转换为使用String.to_existing_atom

答案 5 :(得分:4)

这是@ emaillenin的模块形式的答案版本:

defmodule App.Utils do

  # Implementation based on: http://stackoverflow.com/a/31990445/175830
  def map_keys_to_atoms(map) do
    for {key, val} <- map, into: %{}, do: {String.to_atom(key), val}
  end

  def map_keys_to_strings(map) do
    for {key, val} <- map, into: %{}, do: {Atom.to_string(key), val}
  end

end

答案 6 :(得分:4)

下面的代码片段将嵌套的类似于json的映射的键转换为现有原子:

iex(2)> keys_to_atoms(%{"a" => %{"b" => [%{"c" => "d"}]}})

%{a: %{b: [%{c: "d"}]}}
  def keys_to_atoms(json) when is_map(json) do
    Map.new(json, &reduce_keys_to_atoms/1)
  end

  def reduce_keys_to_atoms({key, val}) when is_map(val), do: {String.to_existing_atom(key), keys_to_atoms(val)}
  def reduce_keys_to_atoms({key, val}) when is_list(val), do: {String.to_existing_atom(key), Enum.map(val, &keys_to_atoms(&1))}
  def reduce_keys_to_atoms({key, val}), do: {String.to_existing_atom(key), val}

答案 7 :(得分:3)

首先,@ Olshansk的回答对我来说就像一个魅力。谢谢你。

接下来,由于@Olshansk提供的初始实现缺少对地图列表的支持,因此,下面是我的代码片段对此进行了扩展。

  def keys_to_atoms(string_key_map) when is_map(string_key_map) do
    for {key, val} <- string_key_map, into: %{}, do: {String.to_atom(key), keys_to_atoms(val)}
  end

  def keys_to_atoms(string_key_map_list) when is_list(string_key_list) do
    string_key_list
    |> Enum.map(&keys_to_atoms/1)
  end

  def keys_to_atoms(value), do: value

这是我使用的示例,随后是将其传递给上述函数后的输出-keys_to_atoms(attrs)

# Input
%{
  "school" => "School of Athens",
  "students" => [
    %{
      "name" => "Plato",
      "subjects" => [%{"name" => "Politics"}, %{"name" => "Virtues"}]
    },
    %{
      "name" => "Aristotle",
      "subjects" => [%{"name" => "Virtues"}, %{"name" => "Metaphysics"}]
    }
  ]
}

# Output
%{
  school: "School of Athens",
  students: [
    %{name: "Plato", subjects: [%{name: "Politics"}, %{name: "Virtues"}]},
    %{name: "Aristotle", subjects: [%{name: "Virtues"}, %{name: "Metaphysics"}]}
  ]
}

对此的解释非常简单。第一种方法是为类型映射的输入而调用的所有内容的心脏。 for循环对键值对中的属性进行解构,并返回键的原子表示形式。 接下来,在返回值的同时,又有三种可能性。

  1. 该值是另一张地图。
  2. 该值是地图列表。
  3. 该值不是上面的值,它是原始值。

因此,这一次,当在分配值的同时调用keys_to_atoms方法时,它可能会根据输入的类型调用这三个方法之一。 方法在代码段中的组织顺序相似。

希望这会有所帮助。干杯!

答案 8 :(得分:1)

defmodule Service.MiscScripts do

@doc """
Changes String Map to Map of Atoms e.g. %{"c"=> "d", "x" => %{"yy" => "zz"}} to
        %{c: "d", x: %{yy: "zz"}}, i.e changes even the nested maps.
"""

def  convert_to_atom_map(map), do: to_atom_map(map)

defp to_atom_map(map) when is_map(map), do: Map.new(map, fn {k,v} -> {String.to_atom(k),to_atom_map(v)} end)     
defp to_atom_map(v), do: v

end

答案 9 :(得分:1)

m = %{"key" => "value", "another_key" => "another_value"}
k = Map.keys(m)|> Enum.map(&(String.to_atom(&1)))
v = Map.values(m)
result = Enum.zip(k, v) |> Enum.into(%{})

答案 10 :(得分:1)

这是我用来递归地(1)格式化地图键作为snakecase和(2)将它们转换为原子。请注意,您应该从不将非白名单用户数据转换为原子,因为它们不会被垃圾回收。

defp snake_case_map(map) when is_map(map) do
  Enum.reduce(map, %{}, fn {key, value}, result ->
    Map.put(result, String.to_atom(Macro.underscore(key)), snake_case_map(value))
  end)
end
defp snake_case_map(list) when is_list(list), do: Enum.map(list, &snake_case_map/1)
defp snake_case_map(value), do: value

答案 11 :(得分:0)

在另一张地图中有一张地图时

def keys_to_atom(map) do
 Map.new(
  map,
  fn {k, v} ->
    v2 = cond do
      is_map(v) -> keys_to_atom(v)
      v in [[nil], nil] -> nil
      is_list(v) -> Enum.map(v, fn o -> keys_to_atom(o) end)
      true -> v
    end
    {String.to_atom("#{k}"), v2}
  end
 )
end

示例:

my_map = %{"a" => "1", "b" => [%{"b1" => "1"}], "c" => %{"d" => "4"}}

结果

%{a: "1", b: [%{b1: "1"}], c: %{d: "4"}}

注意:当您使用“ b” => [1,2,3]时,is_list将失败,因此,在这种情况下,您可以注释/删除此行:

# is_list(v) -> Enum.map(v, fn o -> keys_to_atom(o) end)

答案 12 :(得分:0)

我真的很喜欢Roman Bedichevskii的回答……但是我需要一些能够彻底雾化深层嵌套yaml文件密钥的东西。这是我想出的:

   @doc """
   Safe version, will only atomize to an existing key
   """
   def atomize_keys(map) when is_map(map), do: Map.new(map, &atomize_keys/1)
   def atomize_keys(list) when is_list(list), do: Enum.map(list, &atomize_keys/1)
   def atomize_keys({key, val}) when is_binary(key),
     do: atomize_keys({String.to_existing_atom(key), val})
   def atomize_keys({key, val}), do: {key, atomize_keys(val)}
   def atomize_keys(term), do: term

   @doc """
   Unsafe version, will atomize all string keys
   """
   def unsafe_atomize_keys(map) when is_map(map), do: Map.new(map, &unsafe_atomize_keys/1)
   def unsafe_atomize_keys(list) when is_list(list), do: Enum.map(list, &unsafe_atomize_keys/1)
   def unsafe_atomize_keys({key, val}) when is_binary(key),
     do: unsafe_atomize_keys({String.to_atom(key), val})
   def unsafe_atomize_keys({key, val}), do: {key, unsafe_atomize_keys(val)}
   def unsafe_atomize_keys(term), do: term

它的主要局限性在于,如果您向它提供一个元组{key,value}并且该键是二进制的,它将雾化它。这是关键字列表所需要的,但这可能是某人的特例。在任何情况下,YAML和JSON文件都没有元组的概念,因此对于处理它们而言,没有关系。

答案 13 :(得分:0)

您可以使用 Jason 库。

michalmuskala/jason

%{"key" => "1", "array" => [%{"key" => "1"}], "inner_map" => %{"another_inner_map" => %{"key" => 100}}}
|> Jason.encode!
|> Jason.decode!(keys: :atoms)

%{array: [%{key: "1"}], inner_map: %{another_inner_map: %{key: 100}}, key: "1"}