Inserting multiple records with String keys

I’m using Multi.insert_all to perform bulk inserts and I’m wondering if a list of maps with string keys are supported?

Currently I have the code below transform each string keys to atom.

  defp build_checklist_items(id, attrs) do
    items = Map.get(attrs, "items", [])

    Enum.map(items, fn item ->
      Map.new(item, fn {key, value} ->
        {String.to_atom(key), value}
      end)
      |> Map.merge(%{
        checklist_id: id,
        inserted_at: DateTime.utc_now(),
        updated_at: DateTime.utc_now()
      })
    end)
  end

If string keys are not supported, can I ask for your suggestions if there are idiomatic ways to convert map keys and merges it to new map after.

The String.to_atom/1 call in your code is dangerous if the data is coming from the user or some other dynamic source. Atoms are not garbage collected, so a caller could send different atoms until your VM’s atom table fills up and then it crashes. (Maybe we should rename it to String.dangerous_to_atom/1?)

Since you know your schema / DB structure, you should know what fields exist in the DB. So you should be able to write an explicit mapping function from string keys to atom keys, that you can use before insert_all.

1 Like

So the safer way is to explicitly create a map with atom keys from user input?

From this one:

 Enum.map(items, fn item ->
      Map.new(item, fn {key, value} ->
        {String.to_atom(key), value}
      end)
      |> Map.merge(%{
        checklist_id: id,
        inserted_at: DateTime.utc_now(),
        updated_at: DateTime.utc_now()
      })
    end)
  end

to:

 Enum.map(items, fn item ->
    %{field1: item["field1"], 
     field2: item["field2"], 
     checklist_id: id, 
     inserted_at: DateTime.utc_now(), 
     updated_at: DateTime.utc_now()
   }
  end)
1 Like

That’s a tuple (missing %?), but yes. Now it’s clear which fields will be included and there is no danger of creating new atoms.

You can’t get a map with atom keys from an end-user because an end-user can be malicious. You can call String.to_existing_atom, but I suggest you try maps with string keys first.