【问题标题】:How to convert map keys from strings to atoms in Elixir如何在 Elixir 中将映射键从字符串转换为原子
【发布时间】:2015-11-06 12:48:11
【问题描述】:

Elixir 中%{"foo" => "bar"} 转换成%{foo: "bar"} 的方法是什么?

【问题讨论】:

  • 警告:[String.to_atom/1] 动态创建原子,原子不会被垃圾回收。因此,字符串不应是不受信任的值,例如从套接字接收的输入或 Web 请求期间的输入。考虑改用 to_existing_atom/1 。 hexdocs.pm/elixir/String.html#to_atom/1

标签: elixir


【解决方案1】:

使用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"}

【讨论】:

  • 如何将其存储到某个变量中!
  • 如何将下面转换成原子串 [%{"foo" => "bar", "hello" => "world"},%{"foo" => "baromater", "你好” => “不”}]
  • 这里是一个递归助手的函数定义:def keys_to_atoms(string_key_map) when is_map(string_key_map) do for {key, val} &lt;- string_key_map, into: %{}, do: {String.to_atom(key), keys_to_atoms(val)} enddef keys_to_atoms(value), do: value
  • 警告!您不应该在不受信任的用户输入上调用它,因为 atom 不会被垃圾收集,并且可能会导致您遇到 BEAM 上允许的 atom 数量的限制:hexdocs.pm/elixir/String.html#to_atom/1
  • 请注意,这不会转换深度/递归
【解决方案2】:

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

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

%{a: 1, b: 2}

【讨论】:

  • 我喜欢这个。因为如果你是管道,使用 for 循环会变得很困难。
  • 请注意,这不会转换深度/递归
  • 也可能更好地使用String.to_existing_atom/1(请参阅String.to_existing_atom/1的文档)
【解决方案3】:

您可以使用Enum.reduce/3String.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 来防止这种情况发生。

【讨论】:

  • 是的,我知道内存泄漏,在 ruby​​ 2.2 之前也是如此,但还是谢谢你
  • 是的,在 Ruby 中也是如此,但 Elixir 不是 Ruby,通常这种模式在 Elixir 中是非常不鼓励的。我认为唯一有意义的情况是在将数据加载到结构中时(然后有更安全的方法来做到这一点)
  • @JoséValim 将数据加载到结构中更安全的方法是什么?
【解决方案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}

【讨论】:

  • 在我看来,这篇写得最好。
【解决方案5】:

要基于@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

【讨论】:

    【解决方案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

    【讨论】:

    • 这是如何递归的? atomog(%{"a" =&gt; %{"b" =&gt; 2}}) 返回%{a: %{"b" =&gt; 2}}
    • atomog 函数作为递归函数的一部分被调用,它本身不是递归的。更多细节请查看 morphix 代码本身。
    【解决方案7】:

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

    接下来,由于@Olshansk 提供的初始实现缺乏对地图列表的支持,下面是我的代码 sn-p 扩展它。

    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 循环解构键值对中的属性并返回键的原子表示。 接下来,在返回值的同时,又出现了三种可能。

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

    所以这一次,当keys_to_atoms方法在赋值时被调用,它可能会根据输入的类型调用这三个方法之一。 这些方法在 sn-p 中以类似的顺序组织。

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

    【讨论】:

      【解决方案8】:

      这是@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
      

      【讨论】:

        【解决方案9】:
        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
        

        【讨论】:

        • 完美的是您正在处理嵌套地图(又名递归)
        【解决方案10】:
        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(%{})
        

        【讨论】:

          【解决方案11】:

          您可以使用 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"}
          

          【讨论】:

            【解决方案12】:

            我真的很喜欢 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】:

              这是我用来递归地 (1) 将映射键格式化为蛇形盒 (2) 将它们转换为原子的方法。请记住,您应该切勿将未列入白名单的用户数据转换为 atom,因为它们不会被垃圾回收。

              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
              

              【讨论】:

                【解决方案14】:

                我喜欢使用Enum.into/3,这样我可以轻松地在地图、关键字或任何其他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"]
                

                【讨论】:

                  【解决方案15】:

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

                  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)
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2014-03-10
                    • 2021-12-25
                    • 2016-10-20
                    • 2023-03-17
                    • 2020-06-29
                    相关资源
                    最近更新 更多