WIP refactor gmser_dyn

This commit is contained in:
Ulf Wiger
2026-02-15 12:12:04 +01:00
parent dda5cac7a9
commit 8734e67c66
5 changed files with 306 additions and 72 deletions
+76 -2
View File
@@ -49,8 +49,25 @@ range from 10 to 200, and also to stay within 1 byte.)
When encoding `map` types, the map elements are first sorted.
When specifying a map type for template-driven encoding, use
the `#{items => [{Key, Value}]}` construct.
the `#{items => [{Key, ValueType} | {opt, Key, ValueType}]}` construct.
The key names are included in the encoding, and are match against the item
specs during decoding. If the key names don't match, the decoding fails, unless
for an `{opt, K, V}` item, in which case that item spec is skipped.
```erlang
T = #{items => [{a,int},{opt,b,int},{c,int}]}
E1 = gmser_dyn:encode_typed(T, #{a => 1, b => 2, c => 3}) ->
[<<0>>,<<1>>,[<<252>>,
[[[<<255>>,<<97>>],[<<248>>,<<1>>]],
[[<<255>>,<<98>>],[<<248>>,<<2>>]],
[[<<255>>,<<99>>],[<<248>>,<<3>>]]]]]
E2 = gmser_dyn:encode_typed(T, #{a => 1, c => 3}) ->
[<<0>>,<<1>>,[<<252>>,
[[[<<255>>,<<97>>],[<<248>>,<<1>>]],
[[<<255>>,<<99>>],[<<248>>,<<3>>]]]]]
gmser_dyn:decode_typed(T,E2) ->
#{c => 3,a => 1}
```
## Labels
@@ -119,6 +136,10 @@ inserting the top-level tag), leading to some compression of the output.
This also means that the serialized term cannot be decoded without the same
schema information on the decoder side.
In some cases, the type tags will still be emitted. These are when alternative types
appear, and for enumerated map types (`#{items => ...}`). In the latter case, it is
due to the support for optional items.
In the case of a directly provided template, all type information is inserted,
such that the serialized term can be decoded without any added type information.
The template types are still enforced during encoding.
@@ -135,10 +156,14 @@ ET(lt2i, [{1,2}]) -> [<<0>>,<<1>>,[<<3,232>>,[[<<1>>,<<2>>]]]]
### Alternative types
The dynamic encoder supports two additions to the `gmserialization` template
language: `any` and `#{alt => [AltTypes]}`.
language: `any`, `#{alt => [AltTypes]}` and `#{switch => [AltTypes]}`.
#### `any`
The `any` type doesn't have an associated code, but enforces dynamic encoding.
#### `alt`
The `#{alt => [Type]}` construct also enforces dynamic encoding, and will try
to encode as each type in the list, in the specified order, until one matches.
@@ -150,6 +175,55 @@ gmser_dyn:encode_typed(anyint,-5) -> [<<0>>,<<1>>,[<<246>>,[<<247>>,<<5>>]]]
gmser_dyn:encode_typed(anyint,5) -> [<<0>>,<<1>>,[<<246>>,[<<248>>,<<5>>]]]
```
#### `switch`
The `switch` type allows for encoding a 'tagged' object, where the tag determines
the type.
```erlang
E1 = gmser_dyn:encode_typed(#{switch => #{name => binary, age => int}}, #{age => 29}) ->
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<97,103,101>>],[<<248>>,<<29>>]]]]]
gmser_dyn:decode_typed(#{switch => #{name => binary, age => int}}, E1) ->
#{age => 29}
E2 = gmser_dyn:encode_typed(#{switch => #{name => binary, age => int}}, #{name => <<"Ulf">>}) ->
[<<0>>,<<1>>,[<<252>>,[[[<<255>>,<<110,97,109,101>>],[<<249>>,<<85,108,102>>]]]]]
gmser_dyn:decode_typed(#{switch => #{name => binary, age => int}}, E1) ->
#{name => <<"Ulf">>}
```
A practical use of `switch` would be in a protocol schema:
```erlang
t_msg(_) ->
#{switch => #{ call => t_call
, reply => t_reply
, notification => t_notification }}.
t_call(_) ->
#{items => [ {id, anyint}
, {req, t_req} ]}.
t_reply(_) ->
#{alt => [#{items => [ {id, anyint}
, {result, t_result} ]},
#{items => [ {id, anyint}
, {code, anyint}
, {message, binary} ]}
]}.
```
In this scenario, messages are 'taggged' as 1-element maps, e.g.:
```erlang
async_request(Msg) ->
Id = erlang:unique_integer(),
gmmp_cp:to_server(
whereis(gmmp_core_connector),
#{call => #{ id => Id
, req => Msg }}),
Id.
```
### Notes
Note that `anyint` is a standard type. The static serializer supports only