Encoding & Decoding Abilities
An example for how to implement the builtin Encoding
and Decoding
abilities for an opaque type (ItemKind
).
Implementing these abilites for an opaque type like ItemKind
, enables it to be used seamlessly within other data structures.
This is useful when you would like to provide a custom mapping, such as in this example, between an integer and a tag union.
Implementation
ItemKind := [ Text, Method, Function, Constructor, Field, Variable, Class, Interface, Module, Property, ] implements [ Decoding { decoder: decode_items }, Encoding { to_encoder: encode_items }, Inspect, Eq, ] encode_items : ItemKind -> Encoder fmt where fmt implements EncoderFormatting encode_items = |@ItemKind(kind)| Encode.u32( when kind is Text -> 1 Method -> 2 Function -> 3 Constructor -> 4 Field -> 5 Variable -> 6 Class -> 7 Interface -> 8 Module -> 9 Property -> 10, ) decode_items : Decoder ItemKind _ decode_items = Decode.custom( |bytes, fmt| # Helper function to wrap our [tag](https://www.roc-lang.org/tutorial#tags) ok = |tag| Ok(@ItemKind(tag)) bytes |> Decode.from_bytes_partial(fmt) |> try_map_result( |num| when num is 1 -> ok(Text) 2 -> ok(Method) 3 -> ok(Function) 4 -> ok(Constructor) 5 -> ok(Field) 6 -> ok(Variable) 7 -> ok(Class) 8 -> ok(Interface) 9 -> ok(Module) 10 -> ok(Property) _ -> Err(TooShort), ), ) # Converts `DecodeResult U32` to `DecodeResult ItemKind` using a given function try_map_result : DecodeResult U32, (U32 -> Result ItemKind DecodeError) -> DecodeResult ItemKind try_map_result = |decoded, num_to_item_kind_fun| when decoded.result is Err(e) -> { result: Err(e), rest: decoded.rest } Ok(res) -> { result: num_to_item_kind_fun(res), rest: decoded.rest }
Demo
# make a list of ItemKind's original_list : List ItemKind original_list = [ @ItemKind(Text), @ItemKind(Method), @ItemKind(Function), @ItemKind(Constructor), @ItemKind(Field), @ItemKind(Variable), @ItemKind(Class), @ItemKind(Interface), @ItemKind(Module), @ItemKind(Property), ] # encode them into JSON bytes encoded_bytes : List U8 encoded_bytes = Encode.to_bytes(original_list, Json.utf8) # check that encoding is correct expect expected_bytes : List U8 expected_bytes = "[1,2,3,4,5,6,7,8,9,10]" |> Str.to_utf8 encoded_bytes == expected_bytes # decode back to a list of ItemKind's decoded_list : List ItemKind decoded_list = Decode.from_bytes(encoded_bytes, Json.utf8) |> Result.with_default([]) # don't use `Result.with_default([])` for professional applications; check https://www.roc-lang.org/examples/ErrorHandling/README.html # check that decoding is correct expect decoded_list == original_list main! = |_args| # prints decoded items to stdout decoded_list |> List.map(Inspect.to_str) |> Str.join_with("\n") |> Stdout.line!
Output
Run this from the directory that has main.roc
in it:
$ roc dev (@ItemKind Text) (@ItemKind Method) (@ItemKind Function) (@ItemKind Constructor) (@ItemKind Field) (@ItemKind Variable) (@ItemKind Class) (@ItemKind Interface) (@ItemKind Module) (@ItemKind Property)
You can also use roc test
to run the tests.