If I had the following map:
%{
"example[field_one][0][field_name]" => "some value",
"example[field_one][1][field_name]" => "another value",
"example[field_two]" => "third value",
"different_field" => "different_value"
}
How could I convert it the following structure:
%{
"example" => %{
"field_one" => %{
"0" => %{
"field_name" => "some_value"
},
"1" => %{
"field_name" => "another_value"
}
},
"field_two" => "third_value"
},
"different_field" => "different_value"
}
The use case is some values being grabbed out of a form with their name as per the structure used by Phoenix forms. I need to map is back to a nested structure that I can cast with a Changeset. I’ve looked through the Phoenix.HTML.Form codebase and can’t seem to find the function that would do this, so I assume it comes from the browser in the correct format?
Cheers, Jamie
Is this actually from a phoenix form? If so this should be happening automatically.
It’s coming from a Phoenix form, but I’m grabbing the values with JS and sending then back over a socket. I’m hacking together something to replace my current implementation with Drab whilst I wait for LiveView, so thought I’d see if I could solve it with regular JS and channels.
So far I’ve got this:
Enum.reduce(attrs, %{"example" => %{}}, fn {key, value}, acc ->
path =
key
|> String.split(["[", "]"]) # Remove the []
|> Enum.reject(fn x -> x == "" end) # Remove the "" left behind in List
put_in(acc, path, value)
end)
but the issue is that I need to specify an empty map when creating the accumulator. Otherwise I get an error trying to insert the first nested key on nil.
To update, I found this question answered by @sasajuric. Modifying the answer to my specific situation I ended up with this, which gives me the result I was looking for.
def to_map(input) do
Enum.reduce(input, %{},
fn({key, value}, intermediate_map) ->
merge(intermediate_map, create_path(key), value)
end)
end
defp create_path(key) do
key
|> String.split(["[", "]"]) # Remove the []
|> Enum.reject(fn x -> x == "" end) # Remove the "" left behind in List
end
defp merge(map, [leaf], value), do: Map.put(map, leaf, value)
defp merge(map, [node | remaining_keys], value) do
inner_map = merge(Map.get(map, node, %{}), remaining_keys, value)
Map.put(map, node, inner_map)
end
When trying other methods, it was the creation of the empty map on keys with other nested values that caused me issues. I was trying to solve the problem by using the Kernel’s put_in and get_and_update_in functions which really don’t like nil values!
Sasa’s solution treats the keys as a list, and generates the inner_map first. I was trying to do it the other way, using Enum.reduce and lots of Kernel functions.
Every day’s a school day!
But doesn’t Phoenix have helpers that parse such HTTP-friendly structures?
It must do, but I can’t seem to find them. They obviously exist somewhere between the form submission and the controller params, but where…
Even if I could use them, it would feel odd to use them in this context; although referencing the “official” way this gets done would be interesting.
Why would it be odd though? These are obviously HTTP form parameters. It would make perfect sense to call a Phoenix helper for it… if we could actually find it, that is. 
If it’s a Phoenix helper then yeah, that’d be great and make sense. My search took me into Plug.Parser at which point I decided I didn’t have enough coffee onboard and to just work it out myself! 
I haven’t tried it, but I suspect a healthy dose of Plug.Conn.Query.decode/1 would help.
Update. I tried it now 
input = %{
"example[field_one][0][field_name]" => "some value",
"example[field_one][1][field_name]" => "another value",
"example[field_two]" => "third value",
"different_field" => "different_value"
}
Enum.reduce(input, %{}, fn {k,v}, acc -> Plug.Conn.Query.decode("#{k}=#{v}",
acc)end)
yields
%{
"different_field" => "different_value",
"example" => %{
"field_one" => %{
"0" => %{"field_name" => "some value"},
"1" => %{"field_name" => "another value"}
},
"field_two" => "third value"
}
}
It’d be easier if your input weren’t already broken on the equals sign.
Ironically, I find lack of caffeination tends to drive me toward finding the code that already does it rather that brute force a new way.
2nd edit:
Oh, we can simplify it even more!
Enum.reduce(input, %{}, &Plug.Conn.Query.decode_pair/2)
I knew it! I’m bookmarking Plug.Conn.Query.
BTW, I’m also much more inclined to reuse people’s open-sourced work, and with me it’s not related to coffee at all. It’s just the right thing to do!
Thank you.

That’s brilliant, thank you! Can confirm that it “Just Works”
I’m actually managing the serialising of the form data in the browser, so could opt for a = separated string, but with the decode_pair option I’ll probably leave it as is. Thanks again!