So konvertieren Sie Map-Schlüssel in Elixir von Strings in Atome


77

Was ist der Weg zu konvertieren , %{"foo" => "bar"}um %{foo: "bar"}in Elixir?


2
Warnung: [ String.to_atom/1] erzeugt Atome dynamisch und Atome werden nicht durch Müll gesammelt. Daher sollte die Zeichenfolge kein nicht vertrauenswürdiger Wert sein, z. B. eine Eingabe, die von einem Socket oder während einer Webanforderung empfangen wird. Verwenden Sie stattdessen to_existing_atom / 1. hexdocs.pm/elixir/String.html#to_atom/1
vaer-k

Antworten:


92

Verwendung 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
wie man es in einer Variablen speichert!
Rizwan Patel

2
wie man unten in Atomketten umwandelt [% {"foo" => "bar", "hallo" => "world"},% {"foo" => "baromater", "hallo" => "nope"} ]
Rizwan Patel

2
Hier ist eine Funktionsdefinition für einen rekursiven Helfer: 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(value), do: value
Olshansk

9
Warnung! Sie sollten dies nicht bei nicht vertrauenswürdigen Benutzereingaben aufrufen, da bei Atomen kein Müll gesammelt wird und Sie möglicherweise an
Jason Axelson

1
Beachten Sie nur, dass dies nicht tief / rekursiv konvertiert
sn3p

52

Ich denke, der einfachste Weg, dies zu tun, ist Map.new:

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

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

Ich mag das. Denn wenn Sie Rohrleitungen verwenden, wird es mit der for-Schleife schwierig.
Kaushik Evani

Beachten Sie nur, dass dies nicht tief / rekursiv konvertiert
sn3p

23

Sie können eine Kombination aus Enum.reduce / 3 und String.to_atom / 1 verwenden

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

Sie sollten jedoch vorsichtig sein, wenn Sie aufgrund von Benutzereingaben in Atome konvertieren, da diese nicht durch Müll gesammelt werden, was zu einem Speicherverlust führen kann. Siehe dieses Problem .

Sie können String.to_existing_atom / 1 verwenden , um dies zu verhindern, wenn das Atom bereits vorhanden ist.


Ja, ich weiß über Speicherverlust, dasselbe war vor 2.2 in Ruby, aber trotzdem danke
NoDisplayName

10
Ja, es war dasselbe in Ruby, aber Elixir ist nicht Ruby und im Allgemeinen wird dieses Muster in Elixir extrem entmutigt. Der einzige Fall, den ich für sinnvoll halten kann, ist das Laden von Daten in Strukturen (und dann gibt es sicherere Möglichkeiten)
José Valim

11
@ JoséValim Was ist der sicherere Weg, um Daten in Strukturen zu laden?
Letseatlunch

7

Um auf der Antwort von @ emaillenin aufzubauen, können Sie überprüfen, ob die Schlüssel bereits Atome sind, um zu vermeiden, ArgumentErrordass String.to_atom einen Schlüssel erhält, der bereits ein Atom ist.

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

5

Hierfür gibt es eine Bibliothek, https://hex.pm/packages/morphix . Es hat auch eine rekursive Funktion für eingebettete Schlüssel.

Die meiste Arbeit wird in dieser Funktion erledigt:

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

Welches heißt rekursiv. Nachdem ich die Antwort von @ Galzer gelesen habe, werde ich sie wahrscheinlich String.to_existing_atombald konvertieren .


Wie ist das rekursiv? atomog(%{"a" => %{"b" => 2}})kehrt zurück%{a: %{"b" => 2}}
sn3p

Die Atomog-Funktion wird als Teil einer rekursiven Funktion aufgerufen, sie ist selbst nicht rekursiv. Überprüfen Sie den Morphix-Code selbst auf weitere Details.
Philosodad

4

Hier ist eine Version der Antwort von @ emaillenin in Modulform:

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

4

Zunächst einmal hat die Antwort von @ Olshansk für mich wie ein Zauber gewirkt. Danke für das.

Da die anfängliche Implementierung von @Olshansk die Liste der Karten nicht unterstützte , ist unten mein Code-Snippet aufgeführt, das dies erweitert.

  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

Dies ist das Beispiel, das ich verwendet habe, gefolgt von der Ausgabe, nachdem es an die obige Funktion übergeben wurde - 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"}]}
  ]
}

Die Erklärung dafür ist sehr einfach. Die erste Methode ist das Herzstück von allem, was für die Eingabe der Typzuordnung aufgerufen wird. Die for-Schleife zerstört die Attribute in Schlüssel-Wert-Paaren und gibt die Atomdarstellung des Schlüssels zurück. Als nächstes gibt es bei der Rückgabe des Werts wieder drei Möglichkeiten.

  1. Der Wert ist eine weitere Karte.
  2. Der Wert ist eine Liste von Karten.
  3. Der Wert ist keiner der oben genannten, er ist primitiv.

Wenn die keys_to_atomsMethode dieses Mal aufgerufen wird, während ein Wert zugewiesen wird, kann sie je nach Art der Eingabe eine der drei Methoden aufrufen. Die Methoden sind im Snippet in ähnlicher Reihenfolge angeordnet.

Hoffe das hilft. Prost!


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

1
Perfekt ist, dass Sie es mit verschachtelten Karten zu tun haben (auch bekannt als Rekursion)
sn3p

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(%{})

1

Hier ist, was ich verwende, um rekursiv (1) Kartenschlüssel als Schlangenfall zu formatieren und (2) sie in Atome umzuwandeln. Beachten Sie, dass Sie niemals nicht auf der Whitelist stehende Benutzerdaten in Atome konvertieren sollten , da diese nicht durch Müll gesammelt werden.

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

1

Das folgende Snippet konvertiert Schlüssel einer verschachtelten json-ähnlichen Karte in vorhandene Atome:

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}

0

wenn Sie eine Karte in einer anderen Karte haben

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

Stichprobe:

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

Ergebnis

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

Hinweis: Die is_list schlägt fehl, wenn Sie "b" => [1,2,3] haben, sodass Sie diese Zeile kommentieren / entfernen können, wenn dies der Fall ist:

# is_list(v) -> Enum.map(v, fn o -> keys_to_atom(o) end)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.