Nestru (Nestru v0.1.0) View Source
A library to serialize between maps and nested structs.
Turns map of any shape into a model of nested structs according to hints given to the library. Turns any nested struct into a map.
The library's primary purpose is to serialize between JSON map and an application model; at the same time, the map can be of any origin.
Typical usage looks like the following:
defmodule Order do
@derive Nestru.Encoder
defstruct [:id, :items, :total]
# Giving a hint to Nestru how to process the items list of structs
# and the total struct, other fields go to struct as is.
defimpl Nestru.Decoder do
def from_map_hint(_value, _context, _map) do
{:ok, %{
items: &Nestru.from_list_of_maps(&1, LineItem),
total: Total
}}
end
end
end
defmodule LineItem do
@derive [Nestru.Decoder, Nestru.Encoder]
defstruct [:amount]
end
map = %{
"id" => "A548",
"items" => [%{"amount" => 150}, %{"amount" => 350}],
"total" => %{"sum" => 500}
}
{:ok, model} = Nestru.from_map(map, Order)
{:ok,
%OrderA{
id: "A548",
items: [%LineItemA{amount: 150}, %LineItemA{amount: 350}],
total: %Total{sum: 500}
}}
And going back to the map is as simple as that:
map = Nestru.to_map(model)
%{
id: "A548",
items: [%{amount: 150}, %{amount: 350}],
total: %{sum: 500}
}
Maps with different key names
In some cases, the map's keys have slightly different names compared
to the target's struct field names. Fields that should be decoded into the struct
can be gathered by adopting Nestru.PreDecoder
protocol like the following:
defmodule Quote do
@derive Nestru.Decoder
defstruct [:cost]
defimpl Nestru.PreDecoder do
def gather_fields_map(_value, _context, map) do
{:ok, %{cost: map.cost_value}}
end
end
end
map = %{
"cost_value" => 1280
}
Nestru.from_map(map, Quote)
{:ok, %Quote{cost: 1280}}
Serializing type-dependent fields
To convert a struct with a field that can have the value of multiple struct types into the map and back, the type of the field's value should be persisted. It's possible to do that like the following:
defmodule BookCollection do
defstruct [:name, :items]
defimpl Nestru.Encoder do
def to_map(struct) do
items_kinds = Enum.map(struct.items, fn %module{} ->
module
|> Module.split()
|> Enum.join(".")
end)
items = Enum.map(struct.items, fn item ->
{:ok, map} = Nestru.to_map(item)
map
end)
{:ok, %{name: struct.name, items_kinds: items_kinds, items: items}}
end
end
defimpl Nestru.Decoder do
def from_map_hint(_value, _context, map) do
items_kinds = Enum.map(map.items_kinds, fn module_string ->
module_string
|> String.split(".")
|> Module.safe_concat()
end)
{:ok, %{items: &Nestru.from_list_of_maps(&1, items_kinds)}}
end
end
end
defmodule BookCollection.Book do
@derive [Nestru.Encoder, Nestru.Decoder]
defstruct [:title]
end
defmodule BookCollection.Magazine do
@derive [Nestru.Encoder, Nestru.Decoder]
defstruct [:issue]
end
collection = %BookCollection{
name: "Duke of Norfolk's archive",
items: [
%Book{title: "The Spell in the Chasm"},
%Magazine{issue: "Strange Hunt"}
]
}
{:ok, map} = Nestru.to_map(collection)
{:ok,
%{
name: "Duke of Norfolk's archive",
items_kinds: ["BookCollection.Book", "BookCollection.Magazine"],
items: [%{title: "The Spell in the Chasm"}, %{issue: "Strange Hunt"}]
}}
And restoring of the original nested struct is as simple as that:
{:ok, collection} = Nestru.from_map(map, BookCollection)
{:ok,
%BookCollection{
name: "Duke of Norfolk's archive",
items: [
%Book{title: "The Spell in the Chasm"},
%Magazine{issue: "Strange Hunt"}
]
}}
Use with other libraries
Jason
JSON maps decoded with Jason library are supported with both strings and atoms keys.
Domo
To validate the types of the nested struct values, consider
Domo library that ensures struct's
t()
type and associated preconditions.
Link to this section Summary
Functions
Creates a list of nested structs from the given list of maps.
Similar to from_list_of_maps/2
but checks if enforced struct's fields keys
exist in the given maps.
Creates a nested struct from the given map.
Similar to from_map/3
but checks if enforced struct's fields keys exist
in the given map.
Creates a map from the given nested struct.
Similar to to_map/1
.
Link to this section Functions
Creates a list of nested structs from the given list of maps.
The first argument is a list of maps.
If the second argument is a struct's module atom, then the function calls
the from_map/3
on each input list item.
If the second argument is a list of struct module atoms, the function
calls the from_map/3
function on each input list item with the module atom
taken at the same index of the second list.
In this case, both arguments should be of equal length.
The third argument is a context value to be passed to implemented
functions of Nestru.PreDecoder
and Nestru.Decoder
protocols.
The function returns a list of structs or the first error from from_map/3
function.
Similar to from_list_of_maps/2
but checks if enforced struct's fields keys
exist in the given maps.
Returns a struct or raises an error.
Creates a nested struct from the given map.
The first argument is a map having key-value pairs. Supports both string and atom keys in the map.
The second argument is a struct's module atom.
The third argument is a context value to be passed to implemented
functions of Nestru.PreDecoder
and Nestru.Decoder
protocols.
To give a hint on how to decode nested struct values or a list of such values
for the given field, implement Nestru.Decoder
protocol for the struct.
Function calls struct/2
to build the struct's value.
Keys in the map that don't exist in the struct are automatically discarded.
Similar to from_map/3
but checks if enforced struct's fields keys exist
in the given map.
Returns a struct or raises an error.
Creates a map from the given nested struct.
Casts each field's value to a map recursively, whether it is a struct or a list of structs.
To give a hint to the function of how to generate a map, implement
Nestru.Encoder
protocol for the struct. That can be used to keep
additional type information for the field that can have a value of various
struct types.
Similar to to_map/1
.
Returns a map or raises an error.