Encoding & Decoding Abilities

An example for how to implement the builtin Encoding and Decoding abilities for an opaque type.

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: decodeItems },
        Encoding { toEncoder: encodeItems },
        Inspect,
        Eq,
    ]

tryMapResult : DecodeResult U32, (U32 -> Result ItemKind DecodeError) -> DecodeResult ItemKind
tryMapResult = \decoded, mapper ->
    when decoded.result is
        Err e -> { result: Err e, rest: decoded.rest }
        Ok res -> { result: mapper res, rest: decoded.rest }

decodeItems : Decoder ItemKind fmt where fmt implements DecoderFormatting
decodeItems = Decode.custom \bytes, fmt ->
    # Helper function to wrap our tag
    ok = \tag -> Ok (@ItemKind tag)

    bytes
    |> Decode.fromBytesPartial fmt
    |> tryMapResult \val ->
        when val 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

encodeItems : ItemKind -> Encoder fmt where fmt implements EncoderFormatting
encodeItems = \@ItemKind val ->
    num =
        when val is
            Text -> 1
            Method -> 2
            Function -> 3
            Constructor -> 4
            Field -> 5
            Variable -> 6
            Class -> 7
            Interface -> 8
            Module -> 9
            Property -> 10
    Encode.u32 num

Demo



# make a list of ItemKind's
originalList : List ItemKind
originalList = [
    @ItemKind Text,
    @ItemKind Method,
    @ItemKind Function,
    @ItemKind Constructor,
    @ItemKind Field,
    @ItemKind Variable,
    @ItemKind Class,
    @ItemKind Interface,
    @ItemKind Module,
    @ItemKind Property,
]

# encode them into JSON
encodedBytes : List U8
encodedBytes = Encode.toBytes originalList Json.utf8

# test we have encoded correctly
expect encodedBytes == originalBytes

# take a JSON encoded list
originalBytes : List U8
originalBytes = "[1,2,3,4,5,6,7,8,9,10]" |> Str.toUtf8

# decode into a list of ItemKind's
decodedList : List ItemKind
decodedList = Decode.fromBytes originalBytes Json.utf8 |> Result.withDefault []

# test we have decoded correctly
expect decodedList == originalList

main =
    # debug print decoded items to stdio
    decodedList
    |> List.map Inspect.toStr
    |> Str.joinWith "\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)

You can also use roc test to run the tests.