Formata.Transform (formata v0.1.0)

Copy Markdown View Source

Explicit, opt-in data transformations.

Encoders never drop, stringify, or replace values on their own. When a value cannot be represented in a target format, call one of these functions (or pass them to Formata.convert/4 with :transforms) to make the loss explicit.

Every transformation returns {:ok, data} or {:error, [%Formata.Error{kind: :transform}]}.

iex> Formata.Transform.drop_nils(%{"a" => 1, "b" => nil})
{:ok, %{"a" => 1}}

iex> Formata.Transform.temporal_to_iso8601(%{"on" => ~D[2024-05-01]})
{:ok, %{"on" => "2024-05-01"}}

Summary

Functions

Applies a list of transformations in order, stopping at the first error.

Removes map entries whose value is nil, recursively. nil list elements are kept, because removing them would shift positions.

Replaces every nil (map values and list elements) with value.

Converts :infinity, :negative_infinity, and :nan to the strings "inf", "-inf", and "nan", recursively.

Converts atom, integer, float, and boolean map keys to strings, recursively.

Converts Date, Time, NaiveDateTime, and DateTime values to ISO 8601 strings, recursively.

Types

transform()

@type transform() ::
  :drop_nils
  | :temporal_to_iso8601
  | :stringify_keys
  | :special_floats_to_strings
  | {:replace_nils, term()}
  | (term() -> {:ok, term()} | {:error, [Formata.Error.t(), ...]})

Functions

apply_all(data, transforms)

@spec apply_all(term(), [transform()]) ::
  {:ok, term()} | {:error, [Formata.Error.t(), ...]}

Applies a list of transformations in order, stopping at the first error.

Each element is a transformation name (:drop_nils, :temporal_to_iso8601, :stringify_keys, :special_floats_to_strings), {:replace_nils, value}, or a one-argument function returning {:ok, data} | {:error, errors}.

iex> Formata.Transform.apply_all(%{"a" => nil, "t" => ~T[10:00:00]}, [:drop_nils, :temporal_to_iso8601])
{:ok, %{"t" => "10:00:00"}}

drop_nils(data)

@spec drop_nils(term()) :: {:ok, term()}

Removes map entries whose value is nil, recursively. nil list elements are kept, because removing them would shift positions.

iex> Formata.Transform.drop_nils(%{"a" => %{"b" => nil, "c" => [nil, 1]}})
{:ok, %{"a" => %{"c" => [nil, 1]}}}

replace_nils(data, value)

@spec replace_nils(term(), term()) :: {:ok, term()}

Replaces every nil (map values and list elements) with value.

iex> Formata.Transform.replace_nils(%{"a" => nil, "b" => [nil]}, "")
{:ok, %{"a" => "", "b" => [""]}}

special_floats_to_strings(data)

@spec special_floats_to_strings(term()) :: {:ok, term()}

Converts :infinity, :negative_infinity, and :nan to the strings "inf", "-inf", and "nan", recursively.

iex> Formata.Transform.special_floats_to_strings(%{"x" => :infinity})
{:ok, %{"x" => "inf"}}

stringify_keys(data)

@spec stringify_keys(term()) :: {:ok, term()} | {:error, [Formata.Error.t(), ...]}

Converts atom, integer, float, and boolean map keys to strings, recursively.

Fails with :key_collision if two keys of one map would become the same string (for example :a and "a"), and with :invalid_key for keys that have no string form (nil, tuples, lists, maps, ...).

iex> Formata.Transform.stringify_keys(%{name: "Ada", tags: %{1 => true}})
{:ok, %{"name" => "Ada", "tags" => %{"1" => true}}}

iex> {:error, [%Formata.Error{code: :key_collision}]} =
...>   Formata.Transform.stringify_keys(%{:a => 1, "a" => 2})

temporal_to_iso8601(data)

@spec temporal_to_iso8601(term()) :: {:ok, term()}

Converts Date, Time, NaiveDateTime, and DateTime values to ISO 8601 strings, recursively.

iex> Formata.Transform.temporal_to_iso8601([~U[2024-01-02 03:04:05Z]])
{:ok, ["2024-01-02T03:04:05Z"]}