One annoying detail about web forms is that on submission (unless you are using changesets) all the values come back as strings. This forces you to have to handle the correct formats before, say, passing those params into a service module to execute some action.
To simplify this process, i recently created a simple guesser function that evaluates the values of a map, namely booleans, integers, floats, dates, and datetimes. It is recursive and can go into lists as well. Check the examples:
@doc """ Function to try to guess the type of a variable and return the guessed value. Some applications might include converting a parameter map coming from a form (all strings) into a proper map with typed values, for instance: %{foo: "123", bar: "true", foobar: "2020-12-13"} would become: %{foo: 123, bar: true, foobar: ~D[2020-12-13]} The function supports simple values (a string, for instance), is also recursive into lists and maps and, if it can't figure out the type, it returns the original value. Will also trim the values before testing so `" 123"`, for instance, becomes `123`. ## Examples ## Simple types iex> to_typed("") "" iex> to_typed("123") 123 iex> to_typed(" 456") 456 iex> to_typed("-123") -123 iex> to_typed("123.345") 123.345 iex> to_typed("-12.3") -12.3 iex> to_typed("true") true iex> to_typed(" true ") true iex> to_typed("false") false iex> to_typed("2020-12-13") ~D[2020-12-13] iex> to_typed("2015-01-23T23:50:07Z") ~U[2015-01-23 23:50:07Z] iex> to_typed("2015-01-23T23:50:07.123+02:30") ~U[2015-01-23 21:20:07.123Z] ## Maps iex> to_typed(%{}) %{} iex> to_typed(%{another_integer: "5345345435"}) %{another_integer: 5345345435} iex> to_typed(%{another_map: %{foo: "true"}}) %{another_map: %{foo: true}} iex> to_typed(%{a_list: ["foo", "true"]}) %{a_list: ["foo", true]} ## Lists iex> to_typed([]) [] iex> to_typed(["123"]) [123] iex> to_typed(["12.3"]) [12.3] iex> to_typed(["true", "false"]) [true, false] iex> to_typed(["true", ["false"]]) [true, [false]] iex> to_typed([%{foo: "1"}, %{foo: "2.5"}]) [%{foo: 1}, %{foo: 2.5}] iex> to_typed(["bla", "bla", "2015-01-23T23:50:07Z", ["12345", %{cool: "true"}]]) ["bla", "bla", ~U[2015-01-23 23:50:07Z], [12345, %{cool: true}]] ## Other iex> to_typed(nil) nil iex> to_typed(123) 123 iex> to_typed("actually_a_string") "actually_a_string" """ @spec to_typed(any()) :: any() def to_typed(map) when is_map(map) do map |> Enum.map(fn {key, value} -> value = cond do is_map(value) -> to_typed(value) is_list(value) -> to_typed(value) true -> to_typed(value) end {key, value} end) |> Map.new() end def to_typed(list) when is_list(list) do Enum.map(list, fn value -> cond do is_map(value) or is_list(value) -> to_typed(value) is_binary(value) -> to_typed(value) true -> value end end) end def to_typed(value) when is_binary(value) do value = String.trim(value) cond do value == "true" -> true value == "false" -> false value =~ ~r/\A\d{4}-\d{2}-\d{2}\z/ -> value |> Timex.parse!("{ISOdate}") |> Timex.to_date() value =~ ~r/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ -> value |> Timex.parse!("{ISO:Extended}") |> Timex.to_datetime() value =~ ~r/\A-?\d+\.\d+\z/ -> String.to_float(value) value =~ ~r/\A-?\d+\z/ -> String.to_integer(value) true -> value end end def to_typed(value), do: value
Hope you find it useful.
Happy Elixir'ing...