defmodule ExAws.Dynamo.Decoder do
@moduledoc """
Decodes a Dynamo response into a struct.
If Dynamo.Decodable is implemented for the struct, it will be called
after the completion of the coercion.
This is important for handling nested maps if you wanted the nested maps
to have atom keys.
"""
alias ExAws.Dynamo.Decodable
def decode(item, as: struct_module) do
item
|> decode
|> binary_map_to_struct(struct_module)
|> Decodable.decode()
end
@doc """
Convert Dynamo format to Elixir
Functions which convert the Dynamo-style values into normal Elixir values.
Use these if you just want the Dynamo result to look more like Elixir without
coercing it into a particular struct.
"""
def decode(%{"BOOL" => true}), do: true
def decode(%{"BOOL" => false}), do: false
def decode(%{"BOOL" => "true"}), do: true
def decode(%{"BOOL" => "false"}), do: false
def decode(%{"NULL" => true}), do: nil
def decode(%{"NULL" => "true"}), do: nil
def decode(%{"B" => value}), do: Base.decode64!(value)
def decode(%{"S" => value}), do: value
def decode(%{"M" => value}), do: value |> decode
def decode(%{"BS" => values}), do: MapSet.new(values)
def decode(%{"SS" => values}), do: MapSet.new(values)
def decode(%{"NS" => values}) do
values
|> Stream.map(&binary_to_number/1)
|> Enum.into(MapSet.new())
end
def decode(%{"L" => values}) do
Enum.map(values, &decode/1)
end
def decode(%{"N" => value}) when is_binary(value), do: binary_to_number(value)
def decode(%{"N" => value}) when value |> is_integer or value |> is_float, do: value
def decode(%{} = item) do
item
|> Enum.reduce(%{}, fn {k, v}, map ->
Map.put(map, k, decode(v))
end)
end
@doc "Attempts to convert a number to a float, and then an integer"
def binary_to_number(binary) when is_binary(binary) do
String.to_float(binary)
rescue
ArgumentError -> String.to_integer(binary)
end
def binary_to_number(binary), do: binary
@doc "Converts a map with binary keys to the specified struct"
def binary_map_to_struct(bmap, module) do
module.__struct__
|> Map.from_struct()
|> Enum.reduce(%{}, fn {k, v}, map ->
Map.put(map, k, Map.get(bmap, Atom.to_string(k), v))
end)
|> Map.put(:__struct__, module)
end
end