nm (nm v0.1.0)
View Sourcenm handles deeply nested maps and lists without rebuilding every enclosing parent by hand.
The workflow is: unnest your data, read and follow refs, update the nodes you need, then nest the
table back into ordinary data.
{ok, T} = nm:unnest(#{<<"name">> => <<"Course A">>, <<"lesson">> => #{<<"title">> => <<"Intro">>}}),
{ok, Root} = nm:get_value(T, root),
{'$nm_ref', LessonId} = maps:get(<<"lesson">>, Root),
{ok, Lesson} = nm:get_value(T, LessonId),
ok = nm:fast_update(T, LessonId, Lesson#{<<"position">> => 1}),
{ok, Nested} = nm:nest(T),
Nested = #{<<"name">> => <<"Course A">>, <<"lesson">> => #{<<"title">> => <<"Intro">>, <<"position">> => 1}}.How it works
Maps and lists are traversed. Each nested map or list is replaced by a nm_ref/0 in its parent
and stored as its own node, so a change to one node doesn't require rebuilding its parents. Pass
the id from the ref, not the whole {'$nm_ref', Id} tuple, to get_value/2, fast_update/3,
update/3, and partial_nest/2.
Tuples stay literal, including any maps or lists inside them. Proplists are a list of tuples, so their outer list is traversed while each tuple entry stays as it is.
Map keys keep their original types.
Ownership
A nested_table/0 is protected and owned by the process that called unnest/1. Make updates
from that process; other processes can read. If the owner terminates, the table is destroyed. Don't
hold a table across a process boundary expecting it to outlive its owner. nest/1 does not dispose
of the table.
Summary
Types
Identifiers for nested structure.
root is the begining of the nested structure, all other inner structures are binaries.
null is used only as parent for root.
A nested structure of maps and lists.
An ETS table configured as set and protected with the unnested structure.
A map with the value of an unnested_structure/0 and it's metadata.
A tag to reference a value that was unnested
A unnested strucutre, any value that was another nested_structure is replaced by nm_ref/0.
Functions
Removes the node identified by the id. Also remove the reference to the node in the parent and all the subtrees of children of the node.
Replaces the value of a node while keeping its children and their relationships.
Returns a node of the table. The node contains the actual value and metadata as the current node id and the parent node id.
Returns the local value of a node. Nested children appear as nm_ref/0 tuples; pass the id
inside a ref to get_value/2 to read that child.
Returns the complete table as an ordinary nested structure, {ok, Nested}.
Returns the subtree starting at the given id as ordinary nested data, {ok, Nested}.
Takes a nested map or list and unnests it into an ETS table, returned as {ok, Table}.
Replaces the value of a node with a new structure. Use it for structural changes.
Types
-type nested_id() :: root | null | binary().
Identifiers for nested structure.
root is the begining of the nested structure, all other inner structures are binaries.
null is used only as parent for root.
A nested structure of maps and lists.
-type nested_table() :: ets:table().
An ETS table configured as set and protected with the unnested structure.
-type nm_node() :: #{ref => nested_id(), parent => nested_id(), value => unnested_structure()}.
A map with the value of an unnested_structure/0 and it's metadata.
ref: thenested_id/0of the structure.parent: thenested_id/0of the parent structure.value: the actualunnested_structure/0value.
-type nm_ref() :: {'$nm_ref', nested_id()}.
A tag to reference a value that was unnested
A unnested strucutre, any value that was another nested_structure is replaced by nm_ref/0.
Functions
-spec delete(nested_table(), nested_id()) -> ok | {error, atom()}.
Removes the node identified by the id. Also remove the reference to the node in the parent and all the subtrees of children of the node.
-spec fast_delete(nested_table(), nested_id()) -> ok | {error, atom()}.
-spec fast_update(nested_table(), nested_id(), unnested_structure()) -> ok | {error, atom()}.
Replaces the value of a node while keeping its children and their relationships.
Use it for literal edits, such as adding or overwriting a field of a map, or for rearranging the
existing refs in a list. A nested map or list added this way is stored as a literal and is not
unnested, so use update/3 for structural changes.
-spec get_node(nested_table(), nested_id()) -> {ok, nm_node()} | {error, atom()}.
Returns a node of the table. The node contains the actual value and metadata as the current node id and the parent node id.
-spec get_value(nested_table(), nested_id()) -> {ok, unnested_structure()} | {error, atom()}.
Returns the local value of a node. Nested children appear as nm_ref/0 tuples; pass the id
inside a ref to get_value/2 to read that child.
-spec nest(nested_table()) -> {ok, nested_structure()} | {error, atom()}.
Returns the complete table as an ordinary nested structure, {ok, Nested}.
It does not consume the table: you can keep working with it after nesting.
-spec partial_nest(nested_table(), nested_id()) -> {ok, nested_structure()} | {error, atom()}.
Returns the subtree starting at the given id as ordinary nested data, {ok, Nested}.
Use it to get a subtree to transform, then pass the result to update/3 on the same id. Like
nest/1, it does not remove anything from the table.
-spec unnest(nested_structure()) -> {ok, nested_table()} | {error, atom()}.
Takes a nested map or list and unnests it into an ETS table, returned as {ok, Table}.
The table is owned by the calling process. Make updates from that process; other processes can read. Owner termination destroys the table.
-spec update(nested_table(), nested_id(), nested_structure()) -> ok | {error, atom()}.
Replaces the value of a node with a new structure. Use it for structural changes.
Children of the node are discarded and the new value's nested data is unnested again. Typical
usage is partial_nest/2, transform the ordinary data, then pass it back here. If you're only
changing literal fields or rearranging existing refs, use fast_update/3 instead.