View Source WuunderUtils.Maps (Wuunder Utils v0.9.0)
Contains a set of helpers to deal with some complex stuff with Maps and Structs
Summary
Functions
Tests if the map or struct is present
Converts keys in maps to atoms. By default, this function will only atomize strings that already exist. Note: skips structs.
Removes a deeply nested set of keys
Acts as Kernel.get_in but can also be used on Structs. Has a lot of more extra functionalities
Creates a map from a given set of fields. The output will always be a string.
Acts as Kernel.put-in but can also be used on Structs. Has a lot of more extra functionalities
Removes a key from a map. Doesn't matter if the key is an atom or string
Deletes a list of keys from a map (and all nested maps, lists) Usefull when you want to scrub out IDs for instance.
Trims an incoming map/list/tuple. Removes all keys that have a nil
value. Structs with an empty value
will reset to the default value. Items in lists and tuples that contain nil values will be deleted.
Flattens a map. This results in a map that just contains one level
Creates a clean map from a given struct.
This function deep structs, maps, lists etc. to a map
and uses a set of default transformers as defined in default_struct_fransform/0
.
Retrieves a key from a map regardless of the key type (atom/string) Note: This function does not generate new atoms on the fly.
Maps a given function over entire structure (map/list/struct/tuple)
Generates an empty map and list from a given set of keys
Tests if the given map only consists of atom keys
Acts as an IndifferentMap. Put a key/value regardless of the key type. If the map contains keys as atoms, the value will be stored as atom: value. If the map contains strings as keys it will store the value as binary: value
Only puts value in map when value is actually nil (not the same as empty)
Only puts value in map when the value is considered empty
Conditionally puts a value to a given map. Depending on the condition or the value, the key+value will be set to the map
Maps a given field from given (if not in map)
Mass maps a given input with aliasses
Converts keys in maps to strings. Note: skips structs. So you need to convert these to maps first.
Types
Functions
@spec any?(Ecto.Association.NotLoaded.t() | nil | map()) :: boolean()
Tests if the map or struct is present
Examples
iex> WuunderUtils.Maps.any?(nil)
false
iex> WuunderUtils.Maps.any?(%{})
false
iex> WuunderUtils.Maps.any?(%{a: 1})
true
iex> WuunderUtils.Maps.any?(%Person{})
true
iex> WuunderUtils.Maps.any?(%Ecto.Association.NotLoaded{})
false
Converts keys in maps to atoms. By default, this function will only atomize strings that already exist. Note: skips structs.
Examples
iex> WuunderUtils.Maps.atomize_keys(%{
...> "first_name" => "Peter",
...> "last_name" => "Griffin",
...> "skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}]
...> })
%{
first_name: "Peter",
last_name: "Griffin",
skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}]
}
iex> WuunderUtils.Maps.atomize_keys(%Person{
...> first_name: "Peter",
...> last_name: "Pan",
...> date_of_birth: ~D[1980-01-02],
...> weight: Decimal.new("81.5"),
...> country: %Country{code: "UK"},
...> time_of_death: ~T[13:37:37],
...> meta: %{"skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}]}
...> })
%Person{
first_name: "Peter",
last_name: "Pan",
date_of_birth: ~D[1980-01-02],
weight: Decimal.new("81.5"),
country: %Country{code: "UK"},
time_of_death: ~T[13:37:37],
meta: %{skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}]}
}
Removes a deeply nested set of keys
Examples
iex> WuunderUtils.Maps.deep_delete(%{"data" => [%{"name" => "Piet"}, %{"name" => "Henk"}]}, "data.0.name")
%{"data" => [%{}, %{"name" => "Henk"}]}
iex> person = %Person{
...> country: %Country{code: "NL"},
...> address: %Address{
...> street: "Teststreet",
...> company: %Company{name: "Wuunder"}
...> },
...> meta: %{
...> skills: [
...> "programmer",
...> "manager",
...> %{type: "hobby", name: "painting", grades: {"A+", "C"}}
...> ]
...> }
...> }
...>
...> WuunderUtils.Maps.deep_delete(person, [:country, :code])
%Person{
country: %Country{code: ""},
address: %Address{
street: "Teststreet",
company: %Company{name: "Wuunder"}
},
meta: %{
skills: [
"programmer",
"manager",
%{type: "hobby", name: "painting", grades: {"A+", "C"}}
]
}
}
iex> WuunderUtils.Maps.deep_delete(person, "meta.skills.1")
%Person{
country: %Country{code: "NL"},
address: %Address{
street: "Teststreet",
company: %Company{name: "Wuunder"}
},
meta: %{
skills: [
"programmer",
%{type: "hobby", name: "painting", grades: {"A+", "C"}}
]
}
}
Acts as Kernel.get_in but can also be used on Structs. Has a lot of more extra functionalities:
- You can access lists (nested too)
- You can use mixed keys, they can be Atoms or Strings
- You can use a list to access the properties or a string representation
Examples
iex> person = %Person{
...> country: %Country{code: "NL"},
...> address: %Address{
...> street: "Teststreet",
...> company: %Company{name: "Wuunder"}
...> },
...> meta: %{
...> skills: [
...> "programmer",
...> "manager",
...> %{type: "hobby", name: "painting", grades: {"A+", "C"}}
...> ]
...> }
...> }
...>
...> WuunderUtils.Maps.deep_get(person, [:country, :code])
"NL"
iex> WuunderUtils.Maps.deep_get(person, "country.code")
"NL"
iex> WuunderUtils.Maps.deep_get(person, [:address, :company])
%Company{name: "Wuunder"}
iex> WuunderUtils.Maps.deep_get(person, [:address, :company, :name])
"Wuunder"
iex> WuunderUtils.Maps.deep_get(person, [:meta, :skills])
["programmer", "manager", %{name: "painting", type: "hobby", grades: {"A+", "C"}}]
iex> WuunderUtils.Maps.deep_get(person, [:meta, :skills, 1])
"manager"
iex> WuunderUtils.Maps.deep_get(person, "meta.skills.1")
"manager"
iex> WuunderUtils.Maps.deep_get(person, [:meta, :skills, 2, :type])
"hobby"
iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.type")
"hobby"
iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.non_existent")
nil
iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.non_existent", "default")
"default"
iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.grades.0")
"A+"
iex> WuunderUtils.Maps.deep_get(person, "meta.skills.2.grades.2", "none")
"none"
iex> keyword_list = [
...> name: "Henk",
...> last_name: "Jansen",
...> addresses: [
...> %{"street" => "Laan", "number" => 1},
...> %{"street" => "Straat", "number" => 1337}
...> ]
...> ]
...>
iex> WuunderUtils.Maps.deep_get(keyword_list, "name")
"Henk"
iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses")
[%{"number" => 1, "street" => "Laan"}, %{"number" => 1337, "street" => "Straat"}]
iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.0")
%{"number" => 1, "street" => "Laan"}
iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.1.street")
"Straat"
iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.1.other_field", "none")
"none"
iex> WuunderUtils.Maps.deep_get(keyword_list, "addresses.2.other_field", "none")
nil
Creates a map from a given set of fields. The output will always be a string.
Examples
iex> person = %Person{
...> country: %Country{code: "NL"},
...> address: %Address{
...> street: "Teststreet",
...> company: %Company{name: "Wuunder"}
...> },
...> meta: %{
...> skills: [
...> "programmer",
...> "manager",
...> %{type: "hobby", name: "painting"}
...> ]
...> }
...> }
...>
...> WuunderUtils.Maps.deep_get_values(
...> person,
...> [
...> [:country, :code],
...> [:address, :street],
...> [:meta, :skills, 2, :type]
...> ]
...> )
%{
"address" => %{"street" => "Teststreet"},
"country" => %{"code" => "NL"},
"meta" => %{
"skills" => [
%{"type" => "hobby"}
]
}
}
@spec deep_put( map() | struct() | list() | nil, [atom() | String.t()] | String.t(), any() ) :: any()
Acts as Kernel.put-in but can also be used on Structs. Has a lot of more extra functionalities:
- You can access lists (nested too)
- You can use mixed keys, they can be Atoms or Strings
- You can use a list to access the properties or a string representation
Examples
iex> person = %Person{
...> country: %Country{code: "NL"},
...> address: %Address{
...> street: "Teststreet",
...> company: %Company{name: "Wuunder"}
...> },
...> meta: %{
...> skills: [
...> "programmer",
...> "manager",
...> %{type: "hobby", name: "painting"}
...> ]
...> }
...> }
iex> WuunderUtils.Maps.deep_put(person, [:first_name], "Piet")
%Person{person | first_name: "Piet"}
iex> WuunderUtils.Maps.deep_put(person, [:country, :code], "US")
%Person{person | country: %Country{code: "US"}}
iex> WuunderUtils.Maps.deep_put(person, [:meta, :skills, 1], "vaultdweller")
%Person{person | meta: %{skills: ["programmer", "vaultdweller", %{name: "painting", type: "hobby"}]}}
iex> WuunderUtils.Maps.deep_put(person, [:meta, :skills, 2, :name], "walking")
%Person{person | meta: %{skills: ["programmer", "manager", %{name: "walking", type: "hobby"}]}}
iex> WuunderUtils.Maps.deep_put(person, "meta.skills.2.name", "walking")
%Person{person | meta: %{skills: ["programmer", "manager", %{name: "walking", type: "hobby"}]}}
@spec delete(any(), map_key() | non_neg_integer()) :: any()
Removes a key from a map. Doesn't matter if the key is an atom or string
Examples
iex> WuunderUtils.Maps.delete(%{length: 255, weight: 100}, :length)
%{weight: 100}
iex> WuunderUtils.Maps.delete(%{length: 255, weight: 100}, "length")
%{weight: 100}
iex> WuunderUtils.Maps.delete(%{"value" => 50, "currency" => "EUR"}, "currency")
%{"value" => 50}
iex> WuunderUtils.Maps.delete(%{"value" => 50, "currency" => "EUR"}, :currency)
%{"value" => 50}
iex> WuunderUtils.Maps.delete(["a", "b", "c"], 1)
["a", "c"]
iex> WuunderUtils.Maps.delete({"a", "b", "c"}, 1)
{"a", "c"}
iex> country = %Country{code: "NL"}
...>
...> WuunderUtils.Maps.delete(country, :code)
%Country{code: ""}
iex> country = %Country{code: "NL"}
...>
...> WuunderUtils.Maps.delete(country, "code")
%Country{code: ""}
iex> country = %Country{code: "NL"}
...>
...> WuunderUtils.Maps.delete(country, "does_not_exist")
%Country{code: "NL"}
Deletes a list of keys from a map (and all nested maps, lists) Usefull when you want to scrub out IDs for instance.
Examples
iex> WuunderUtils.Maps.delete_all(
...> %{
...> shipment: %{
...> id: "shipment-id",
...> wuunder_id: "WUUNDERID"
...> },
...> order_lines: [
...> %{id: "123", sku: "SKU01"},
...> %{id: "456", sku: "SKU02"},
...> %{id: "789", sku: "SKU03"}
...> ],
...> meta: %{
...> configuration_id: "nothing"
...> }
...> },
...> [:id, :configuration_id]
...> )
%{
meta: %{},
order_lines: [%{sku: "SKU01"}, %{sku: "SKU02"}, %{sku: "SKU03"}],
shipment: %{wuunder_id: "WUUNDERID"}
}
iex> WuunderUtils.Maps.delete_all([
...> %{id: "123", name: "test1"},
...> %{id: "456", name: "test2"},
...> %{id: "789", name: "test3"}
...> ], [:id])
[%{name: "test1"}, %{name: "test2"}, %{name: "test3"}]
Trims an incoming map/list/tuple. Removes all keys that have a nil
value. Structs with an empty value
will reset to the default value. Items in lists and tuples that contain nil values will be deleted.
Examples
iex> WuunderUtils.Maps.delete_empty(%{name: nil, last_name: "Jansen"})
%{last_name: "Jansen"}
iex> WuunderUtils.Maps.delete_empty(%{name: nil, last_name: nil})
nil
iex> WuunderUtils.Maps.delete_empty({1, 2, nil, 3, 4})
{1, 2, 3, 4}
iex> WuunderUtils.Maps.delete_empty([1, 2, nil, 3, 4])
[1, 2, 3, 4]
iex> WuunderUtils.Maps.delete_empty(%{items: [%{a: nil, b: nil}, %{a: 1, b: 2}]})
%{items: [%{a: 1, b: 2}]}
iex> WuunderUtils.Maps.delete_empty(%{items: [%{a: [1, nil, %{x: 1337, y: {1, nil, 2, {nil, nil}}}], b: nil}, %{a: 1, b: 2}]})
%{items: [%{a: [1, %{y: {1, 2}, x: 1337}]}, %{a: 1, b: 2}]}
Flattens a map. This results in a map that just contains one level
Options
- key_separator (default .)
- underscore_key (default true)
- list_index_start (default 1)
Example
iex> WuunderUtils.Maps.flatten(%{
...> test: "123",
...> order_lines: [
...> %{sku: "123", description: "test"},
...> %{sku: "456", description: "test 2"}
...> ],
...> meta: %{
...> data: "test"
...> }
...> })
%{
"test" => "123",
"order_lines.1.sku" => "123",
"order_lines.1.description" => "test",
"order_lines.2.sku" => "456",
"order_lines.2.description" => "test 2",
"meta.data" => "test"
}
iex> WuunderUtils.Maps.flatten({1, 2, 3})
%{
"1" => 1,
"2" => 2,
"3" => 3,
}
iex> WuunderUtils.Maps.flatten([
...> %{sku: "123", description: "test"},
...> %{sku: "456", description: "test 2"}
...> ])
%{
"1.sku" => "123",
"1.description" => "test",
"2.sku" => "456",
"2.description" => "test 2"
}
iex> WuunderUtils.Maps.flatten(
...> %{
...> test: "123",
...> order_lines: [
...> %{sku: "123", description: "test"},
...> %{sku: "456", description: "test 2"}
...> ],
...> meta: %{
...> data: "test"
...> },
...> tuple: {1, 2, 3}
...> },
...> key_separator: "_",
...> list_index_start: 0
...> )
%{
"test" => "123",
"order_lines_0_sku" => "123",
"order_lines_0_description" => "test",
"order_lines_1_sku" => "456",
"order_lines_1_description" => "test 2",
"tuple_0" => 1,
"tuple_1" => 2,
"tuple_2" => 3,
"meta_data" => "test"
}
Creates a clean map from a given struct.
This function deep structs, maps, lists etc. to a map
and uses a set of default transformers as defined in default_struct_fransform/0
.
There is also an option to omit the transform
option to add an extra set of transformers.
Took some inspiration from this great lib: https://github.com/prodis/miss-elixir/blob/0.1.5/lib/miss/map.ex
Note: It's also able to convert Ecto models to flat maps. It uses the defined Ecto fields for that.
Examples
iex> WuunderUtils.Maps.from_struct(%Person{
...> first_name: "Peter",
...> last_name: "Pan",
...> date_of_birth: ~D[1980-01-02],
...> weight: Decimal.new("81.5"),
...> country: %{code: "UK"},
...> time_of_death: ~T[13:37:37]
...> })
%{
address: nil,
date_of_birth: "1980-01-02",
first_name: "Peter",
last_name: "Pan",
time_of_death: "13:37:37",
weight: "81.5",
country: %{code: "UK"},
meta: %{}
}
iex> WuunderUtils.Maps.from_struct(
...> %Person{
...> first_name: "Peter",
...> last_name: "Pan",
...> date_of_birth: ~D[1980-01-02],
...> weight: Decimal.new("81.5"),
...> country: %Country{code: "UK"},
...> time_of_death: ~T[13:37:37]
...> },
...> transform: [{Country, fn x -> "COUNTRY:" <> x.code end}]
...> )
%{
address: nil,
date_of_birth: "1980-01-02",
first_name: "Peter",
last_name: "Pan",
time_of_death: "13:37:37",
weight: "81.5",
country: "COUNTRY:UK",
meta: %{}
}
iex> WuunderUtils.Maps.from_struct(
...> %Person{
...> address: %Address{
...> street: "Straat",
...> number: 13,
...> zipcode: "1122AB"
...> },
...> first_name: "Peter",
...> last_name: "Pan",
...> date_of_birth: ~D[1980-01-02],
...> weight: Decimal.new("81.5"),
...> country: %{code: "UK"},
...> time_of_death: ~T[13:37:37]
...> }
...> )
%{
address: %{company: nil, number: 13, street: "Straat", zipcode: "1122AB"},
date_of_birth: "1980-01-02",
first_name: "Peter",
last_name: "Pan",
time_of_death: "13:37:37",
weight: "81.5",
country: %{code: "UK"},
meta: %{}
}
@spec get(any(), map_key() | non_neg_integer(), any()) :: any()
Retrieves a key from a map regardless of the key type (atom/string) Note: This function does not generate new atoms on the fly.
Examples
iex> WuunderUtils.Maps.get(%{value: 20}, :value)
20
iex> WuunderUtils.Maps.get(%{"value" => 20}, :value)
20
iex> WuunderUtils.Maps.get(%{value: 20}, "value")
20
iex> WuunderUtils.Maps.get(%{value: 20}, "non-existent")
nil
iex> WuunderUtils.Maps.get(%{value: 20}, :weight)
nil
iex> WuunderUtils.Maps.get(%{value: 20}, :weight, 350)
350
iex> WuunderUtils.Maps.get(%{value: 20}, "currency", "EUR")
"EUR"
iex> WuunderUtils.Maps.get([name: "Henk", name: "Kees", last_name: "Jansen"], "name")
"Henk"
iex> WuunderUtils.Maps.get(["a", "b", "c"], 1)
"b"
iex> WuunderUtils.Maps.get(["a", "b", "c"], 3, "d")
"d"
iex> WuunderUtils.Maps.get({"a", "b", "c"}, 1)
"b"
iex> WuunderUtils.Maps.get({"a", "b", "c"}, 3, "d")
"d"
Maps a given function over entire structure (map/list/struct/tuple)
Examples
iex> WuunderUtils.Maps.map(%{name: " test ", data: ["some item", "other item ", %{x: " value"}]}, &WuunderUtils.Presence.trim/1)
%{data: ["some item", "other item", %{x: "value"}], name: "test"}
Generates an empty map and list from a given set of keys
Examples
iex> WuunderUtils.Maps.new([:person, :name, :meta, 0, :hobby, :type])
%{"person" => %{"name" => %{"meta" => [%{"hobby" => %{"type" => %{}}}]}}}
iex> WuunderUtils.Maps.new([:person, :name, :meta, 0, :hobbies, 0, :type])
%{"person" => %{"name" => %{"meta" => [%{"hobbies" => [%{"type" => %{}}]}]}}}
Tests if the given map only consists of atom keys
Examples
iex> WuunderUtils.Maps.only_atom_keys?(%{a: 1, b: 2})
true
iex> WuunderUtils.Maps.only_atom_keys?(%{:a => 1, "b" => 2})
false
iex> WuunderUtils.Maps.only_atom_keys?(%{"a" => 1, "b" => 2})
false
Acts as an IndifferentMap. Put a key/value regardless of the key type. If the map contains keys as atoms, the value will be stored as atom: value. If the map contains strings as keys it will store the value as binary: value
Examples
iex> WuunderUtils.Maps.put(%{value: 20}, :weight, 350)
%{value: 20, weight: 350}
iex> WuunderUtils.Maps.put(["a", "b", "c"], 1, "d")
["a", "d", "c"]
iex> WuunderUtils.Maps.put(["a", "b", "c"], 4, "d")
["a", "b", "c"]
iex> WuunderUtils.Maps.put(%{value: 20, weight: 200}, "weight", 350)
%{value: 20, weight: 350}
iex> WuunderUtils.Maps.put(%{value: 20}, "weight", 350)
%{:value => 20, "weight" => 350}
iex> WuunderUtils.Maps.put(%{"weight" => 350}, :value, 25)
%{"weight" => 350, "value" => 25}
iex> WuunderUtils.Maps.put(%{"weight" => 350}, "value", 25)
%{"weight" => 350, "value" => 25}
Only puts value in map when value is actually nil (not the same as empty)
Examples
iex> WuunderUtils.Maps.put_if_not_nil(%{street: "Straat"}, :street, "Laan")
%{street: "Laan"}
iex> WuunderUtils.Maps.put_if_not_nil(%{street: "Straat"}, :street, nil)
%{street: "Straat"}
iex> WuunderUtils.Maps.put_if_not_nil(%{street: "Straat"}, :street, " ")
%{street: " "}
Only puts value in map when the value is considered empty
Examples
iex> WuunderUtils.Maps.put_if_present(%{street: "Straat"}, :street, "Laan")
%{street: "Laan"}
iex> WuunderUtils.Maps.put_if_present(%{street: "Straat"}, :street, nil)
%{street: "Straat"}
iex> WuunderUtils.Maps.put_if_present(%{street: "Straat"}, :street, " ")
%{street: "Straat"}
Conditionally puts a value to a given map. Depending on the condition or the value, the key+value will be set to the map
Examples
iex> WuunderUtils.Maps.put_when(%{street: "Straat"}, 1 == 1, :number, 13)
%{number: 13, street: "Straat"}
iex> WuunderUtils.Maps.put_when(%{street: "Straat"}, fn -> "value" == "value" end, :number, 13)
%{number: 13, street: "Straat"}
iex> WuunderUtils.Maps.put_when(%{street: "Straat"}, 10 > 20, :number, 13)
%{street: "Straat"}
Maps a given field from given (if not in map)
Examples
iex> WuunderUtils.Maps.rename_key(%{country: "NL"}, :country, :country_code)
%{country_code: "NL"}
iex> WuunderUtils.Maps.rename_key(%{"country" => "NL"}, :country, :country_code)
%{"country_code" => "NL"}
iex> WuunderUtils.Maps.rename_key(%{street_name: "Straatnaam"}, :street, :street_address)
%{street_name: "Straatnaam"}
Mass maps a given input with aliasses
Examples
iex> WuunderUtils.Maps.rename_keys(%{country: "NL", street: "Straat", number: 666}, %{country: :country_code, street: :street_name, number: :house_number})
%{country_code: "NL", house_number: 666, street_name: "Straat"}
Converts keys in maps to strings. Note: skips structs. So you need to convert these to maps first.
Examples
iex> WuunderUtils.Maps.stringify_keys(%{
...> first_name: "Peter",
...> last_name: "Griffin",
...> skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}]
...> })
%{
"first_name" => "Peter",
"last_name" => "Griffin",
"skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}]
}
iex> WuunderUtils.Maps.stringify_keys(%Person{
...> first_name: "Peter",
...> last_name: "Pan",
...> date_of_birth: ~D[1980-01-02],
...> weight: Decimal.new("81.5"),
...> country: %Country{code: "UK"},
...> time_of_death: ~T[13:37:37],
...> meta: %{skills: [%{code: "A", title: "Talking"}, %{code: "B", title: "Moving"}]}
...> })
%Person{
first_name: "Peter",
last_name: "Pan",
date_of_birth: ~D[1980-01-02],
weight: Decimal.new("81.5"),
country: %Country{code: "UK"},
time_of_death: ~T[13:37:37],
meta: %{"skills" => [%{"code" => "A", "title" => "Talking"}, %{"code" => "B", "title" => "Moving"}]}
}