如何在 Elixir 中将映射键从字符串转换为原子
How to convert map keys from strings to atoms in Elixir
在 Elixir 中将 %{"foo" => "bar"}
转换为 %{foo: "bar"}
的方法是什么?
您可以使用 Enum.reduce/3 and String.to_atom/1
的组合
%{"foo" => "bar"}
|> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, String.to_atom(key), val) end)
%{foo: "bar"}
但是,您应该谨慎转换为基于用户输入的原子,因为它们不会被垃圾回收,这可能会导致内存泄漏。参见 this issue。
如果原子已经存在,您可以使用 String.to_existing_atom/1 来防止这种情况。
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"}
以@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
这是模块形式的@emaillenin 答案的一个版本:
defmodule App.Utils do
# Implementation based on:
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
有一个图书馆,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
。
我认为最简单的方法是使用 Map.new
:
%{"a" => 1, "b" => 2}
|> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
%{a: 1, b: 2}
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
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(%{})
这是我用来递归地 (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
首先,@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_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 循环解构键值对中的属性和 returns 键的原子表示。
接下来,在返回值的同时,又出现了三种可能。
- 该值是另一张地图。
- 该值为地图列表。
- 值为上面的none,很原始
所以这次在赋值时调用keys_to_atoms
方法时,可能会根据输入的类型调用三种方法中的一种。
这些方法在代码段中以类似的顺序组织。
希望这对您有所帮助。干杯!
下面的代码片段将嵌套的 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}
当您在另一张地图中有一张地图时
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 会失败,所以你可以 comment/remove 这一行如果是这样的话:
# is_list(v) -> Enum.map(v, fn o -> keys_to_atom(o) end)
我真的很喜欢 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 文件都没有元组的概念,因此对于处理它们来说,这无关紧要。
您可以使用 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"}
我喜欢用Enum.into/3 so that I can easily choose between Map, Keyword or any other Collectable
%{"foo" => "bar"}
|> Enum.into(Map.new(), fn {k, v} -> {String.to_atom(k), v} end)
%{foo: "bar"}
%{"foo" => "bar"}
|> Enum.into(Keyword.new(), fn {k, v} -> {String.to_atom(k), v} end)
[foo: "bar"]
在 Elixir 中将 %{"foo" => "bar"}
转换为 %{foo: "bar"}
的方法是什么?
您可以使用 Enum.reduce/3 and String.to_atom/1
的组合%{"foo" => "bar"}
|> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, String.to_atom(key), val) end)
%{foo: "bar"}
但是,您应该谨慎转换为基于用户输入的原子,因为它们不会被垃圾回收,这可能会导致内存泄漏。参见 this issue。
如果原子已经存在,您可以使用 String.to_existing_atom/1 来防止这种情况。
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"}
以@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
这是模块形式的@emaillenin 答案的一个版本:
defmodule App.Utils do
# Implementation based on:
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
有一个图书馆,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
。
我认为最简单的方法是使用 Map.new
:
%{"a" => 1, "b" => 2}
|> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
%{a: 1, b: 2}
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
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(%{})
这是我用来递归地 (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
首先,@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_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 循环解构键值对中的属性和 returns 键的原子表示。 接下来,在返回值的同时,又出现了三种可能。
- 该值是另一张地图。
- 该值为地图列表。
- 值为上面的none,很原始
所以这次在赋值时调用keys_to_atoms
方法时,可能会根据输入的类型调用三种方法中的一种。
这些方法在代码段中以类似的顺序组织。
希望这对您有所帮助。干杯!
下面的代码片段将嵌套的 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}
当您在另一张地图中有一张地图时
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 会失败,所以你可以 comment/remove 这一行如果是这样的话:
# is_list(v) -> Enum.map(v, fn o -> keys_to_atom(o) end)
我真的很喜欢 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 文件都没有元组的概念,因此对于处理它们来说,这无关紧要。
您可以使用 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"}
我喜欢用Enum.into/3 so that I can easily choose between Map, Keyword or any other Collectable
%{"foo" => "bar"}
|> Enum.into(Map.new(), fn {k, v} -> {String.to_atom(k), v} end)
%{foo: "bar"}
%{"foo" => "bar"}
|> Enum.into(Keyword.new(), fn {k, v} -> {String.to_atom(k), v} end)
[foo: "bar"]