The Concise Binary Object Representation (CBOR) is a data format whose design goals include the possibility of extremely small code size, fairly small message size, and extensibility without the need for version negotiation. These design goals make it different from earlier binary serializations such as ASN.1 and MessagePack.
Describes how to turn a binary CBOR sequence of bytes into any Elm value.
decode : Decoder a -> Bytes -> Maybe a
Turn a binary CBOR sequence of bytes into a nice Elm value.
maybe : Decoder a -> Decoder (Maybe a)
Helpful for dealing with optional items. Turns null
or undefined
into Nothing
.
D.decode (D.maybe D.bool) Bytes<0xF6> == Just Nothing
D.decode (D.maybe D.bool) Bytes<0xF4> == Just (Just False)
bool : Decoder Basics.Bool
Decode a boolean.
D.decode D.bool Bytes<0xF4> == Just False
D.decode D.bool Bytes<0xF5> == Just True
int : Decoder Basics.Int
Decode an integer. Note that there's no granular decoders (nor encoders) because, the CBOR encoding of an integers varies depending on the integer value. Therefore, at the CBOR-level, as in Elm, there's no notion of smaller integers.
D.decode D.int Bytes<0x00> == Just 0
D.decode D.int Bytes<0x00> == Just 0
D.decode D.int Bytes<0x1A, 0x00, 0x02, 0x33, 0x56> == Just 1337
D.decode D.int Bytes<0x2D> == Just -14
bigint : Decoder ( Cbor.Sign, Bytes )
Decode an unbounded integer as a big-endian bytes sequence. This is particularly useful for decoding large integer values which are beyond the max and min safe integer values allowed by Elm.
This can also be used to decode tagged PositiveBigNum
and NegativeBigNum
integer values following the tag semantic specified in RFC-7049.
-- 0
D.decode D.bigint Bytes<0x00>
== Just (Positive, Bytes<0x00>)
-- 1337
D.decode D.bigint Bytes<0x19, 0x05, 0x39>
== Just (Positive, Bytes<0x05, 0x39>)
-- -14
D.decode D.bigint Bytes<0x2D>
== Just (Negative, Bytes<0x0E>)
-- -1536
D.decode D.bigint Bytes<0x39, 0x05, 0xFF>
== Just <Negative, Bytes<0x06, 0x00>
-- 2^53
D.decode D.bigint Bytes<0x1B, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>
== Just (Positive, Bytes<0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>)
-- 2^64
D.decode D.bigint Bytes<0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>
== Just (Positive, Bytes<0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>)
float : Decoder Basics.Float
Decode a floating point number, regardless of its precision. Because in Elm, there exists only one 'Float' type, which is encoded in double-precision, we do decode all floats into this double-precision number in Elm which may lead to some precision gain and therefore, different representation than one would expect.
D.decode D.float <| E.encode (E.float16 1.1) == Just 1.099609375
D.decode D.float <| E.encode (E.float64 1.1) == Just 1.1
string : Decoder String
Decode a bunch UTF-8 bytes into a String
.
In case of streaming, all string chunks are decoded at once and concatenated as if they were
one single string. See also Cbor.Encode.beginString
.
bytes : Decoder Bytes
Decode a bunch bytes into Bytes
.
In case of streaming, all byte chunks are decoded at once and concatenated as if they were
one single byte string. See also Cbor.Encode.beginBytes
.
list : Decoder a -> Decoder (List a)
Decode a List
of items a
. The list can be definite or indefinite.
D.decode (D.list D.int) Bytes<0x82, 0x0E, 0x18, 0x2A> == Just [ 14, 42 ]
D.decode (D.list D.int) Bytes<0x9F, 0x01, 0x02, 0xFF> == Just [ 1, 1 ]
D.decode (D.list (D.list D.bool)) Bytes<0x81, 0x9F, 0xF4, 0xFF> == Just [ [ False ] ]
length : Decoder Basics.Int
Decode only the length of a (definite) CBOR array.
dict : Decoder comparable -> Decoder a -> Decoder (Dict comparable a)
Decode an CBOR map of pairs of data-items into an Elm Dict
.
The map can be either of definite length or indefinite length.
D.decode (D.dict D.int D.int) Bytes<0xA1, 0x0E, 0x18, 0x2A>
== Dict.fromList [ ( 14, 42 ) ]
D.decode (D.dict D.string D.int) Bytes<0xBF, 0x61, 0x61, 0x0E, 0x61, 0x62, 0x18, 0x2A, 0xFF>
== Dict.fromList [ ( "a", 14 ), ( "b", 42 ) ]
size : Decoder Basics.Int
Decode only the size of a (definite) CBOR map.
associativeList : Decoder k -> Decoder v -> Decoder (List ( k, v ))
Decode a CBOR map as an associative list, where keys and values can be arbitrary CBOR.
If keys are comparable
,
you should prefer dict
to this primitive.
fold : Decoder k -> (k -> Decoder (state -> state)) -> state -> Decoder state
Decode a CBOR map by folding over its entries and updating a state variable. Useful to decode record-like structures that have empty values.
type alias Foo =
{ foo : Maybe Int
, bar : Maybe Bool
}
decodeFoo : D.Decoder Foo
decodeFoo =
D.fold D.int
(\k ->
case k of
0 ->
D.int |> D.andThen (\v st -> { st | foo = Just v })
1 ->
D.bool |> D.andThen (\v st -> { st | bar = Just v })
_ ->
D.fail
)
{ foo = Nothing, bar = Nothing }
An intermediate (opaque) step in the decoding of a record. See
record
or tuple
for more detail.
record : Decoder k -> steps -> (Step k steps -> Decoder (Step k record)) -> Decoder record
Decode a (definite) CBOR map into a record. Keys in the map can be arbitrary CBOR but are expected to be homogeneous across the record.
type alias Album =
{ artist : String
, title : String
, label : Maybe String
}
-- In this example, we use compact integer as keys.
decodeAlbumCompact : D.Decoder Album
decodeAlbumCompact =
D.record D.int Album <|
D.fields
>> D.field 0 D.string
>> D.field 1 D.string
>> D.optionalField 2 D.string
-- In this example, we use more verbose string keys.
decodeAlbumVerbose : D.Decoder Album
decodeAlbumVerbose =
D.record D.string Album <|
D.fields
>> D.field "artist" D.string
>> D.field "title" D.string
>> D.optionalField "label" D.string
fields : Step k steps -> Decoder (Step k steps)
A helper that makes writing record decoders nicer. It is equivalent to
succeed
, but let us align decoders to fight compulsory OCDs.
field : k -> Decoder field -> Decoder (Step k (field -> steps)) -> Decoder (Step k steps)
Decode a field of record and step through the decoder. This ensures that the decoded key matches the expected key. If it doesn't, the entire decoder is failed.
See record
for detail about usage.
optionalField : k -> Decoder field -> Decoder (Step k (Maybe field -> steps)) -> Decoder (Step k steps)
Decode an optional field of record and step through the decoder. This
ensures that the decoded key matches the expected key. If it doesn't, the entire
decoder is failed. When the key is missing, returns Nothing
as a value.
See record
for detail about usage.
tuple : steps -> (Step Basics.Never steps -> Decoder (Step Basics.Never tuple)) -> Decoder tuple
Decode a (definite) CBOR array into a record / tuple.
type alias Track =
{ title : String
, duration : Int
}
decodeTrack : D.Decoder Track
decodeTrack =
D.tuple Track <|
D.elems
>> D.elem D.string
>> D.elem D.int
elems : Step Basics.Never steps -> Decoder (Step Basics.Never steps)
A helper that makes writing record decoders nicer. It is equivalent to
succeed
, but let us align decoders to fight compulsory OCDs.
elem : Decoder field -> Decoder (Step Basics.Never (field -> steps)) -> Decoder (Step Basics.Never steps)
Decode an element of a record or tuple and step through the decoder.
See tuple
for detail about usage.
optionalElem : Decoder field -> Decoder (Step Basics.Never (Maybe field -> steps)) -> Decoder (Step Basics.Never steps)
Decode an optional element of a record or tuple and step through the decoder. Note that optional elements only make sense at the end of a structure.
In particular, optional elements must be contiguous and can only be optional (resp. missing) if all their successors are also optional (resp. missing).
For example, consider:
type alias Foo =
Foo
{ a : Int
, b : Maybe Int
, c : Maybe Int
}
decodeFoo =
tuple Foo <|
elems
>> elem int
>> optionalElem int
>> optionalElem int
c
to be missing.b
and c
to be missing.b
to be missing as this is undistinguishable from c
missing.If you need such behavior, use elem
with maybe
.
succeed : a -> Decoder a
Always succeed to produce a certain Elm value.
D.decode (D.succeed 14) Bytes<0x42, 0x28> == Just 14
D.decode (D.list (D.int |> D.andThen (\_ -> D.succeed 1))) Bytes<0x83, 0x00, 0x00, 0x00>
== Just [ 1, 1, 1 ]
This particularly handy when used in combination with andThen
.
fail : Decoder a
A decoder that always fail.
D.decode D.fail Bytes<0x00> == Nothing
This is particularly handy when used in combination with andThen
andThen : (a -> Decoder b) -> Decoder a -> Decoder b
Decode something and then use that information to decode something else. This is useful when a 'Decoder' depends on a value held by another decoder:
tagged : Tag -> Decoder a -> Decoder a
tagged expectedTag decodeA =
tag
|> andThen
(\decodedTag ->
if decodedTag == expectedTag then
decodeA
else
fail
)
ignoreThen : Decoder a -> Decoder ignored -> Decoder a
Decode something and then another thing, but only continue with the second result.
ignoreThen a ignored =
ignored |> andThen (always a)
thenIgnore : Decoder ignored -> Decoder a -> Decoder a
Decode something and then another thing, but only continue with the first result.
thenIgnore ignored a =
a |> andThen (\result -> map (always result) ignored)
map : (a -> value) -> Decoder a -> Decoder value
Transform a decoder. For example, maybe you just want to know the length of a string:
import String
stringLength : Decoder Int
stringLength =
D.map String.length D.string
map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value
Try two decoders and then combine the result. Can be used to decode objects with many fields:
type alias Point =
{ x : Float, y : Float }
point : Decoder Point
point =
D.map2 Point D.float D.float
It tries each individual decoder and puts the result together with the Point constructor.
map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value
Try three decoders and then combine the result. Can be used to decode objects with many fields:
type alias Person =
{ name : String, age : Int, height : Float }
person : Decoder Person
person =
D.map3 Person D.string D.int D.float
Like map2
it tries each decoder in order and then give the results to the
Person
constructor. That can be any function though!
map4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value
Try four decoders and then combine the result. See also 'map3' for some examples.
map5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value
Try five decoders and then combine the result. See also 'map3' for some examples.
traverse : (a -> Decoder b) -> List a -> Decoder (List b)
Apply a decoder to all elements of a List
, in order, and yield the
resulting resulting List
.
oneOf : List (Decoder a) -> Decoder a
Decode something that can have one of many shapes without prior information on the correct shape.
type IntOrString
= IntVariant Int
| StringVariant String
intOrString : Decoder IntOrString
intOrString =
D.oneOf [ D.map IntVariant D.int, D.map StringVariant D.string ]
Bytes<0x64, 0xF0, 0x9F, 0x8C, 0x88>
|> D.decode intOrString
--> (Just <| StringVariant "🌈")
keep : Decoder a -> Decoder (a -> b) -> Decoder b
Decode the next value from a structure, and continue decoding.
This is particularly useful when manually decoding an array-like or map-like structure that materialize multi-variant constructors. For example, imagine the following:
type Hostname
= Single Ipv4 TcpPort
| Multi AaaRecord TcpPort
| Dns SrvRecord
Which could be encoded as such:
toCBOR : Hostname -> E.Encoder
toCBOR host =
case host of
Single ip tcpPort ->
E.sequence
[ E.length 3
, E.int 0
, Ipv4.toCbor ip
, TcpPort.toCbor tcpPort
]
Multi record tcpPort ->
E.sequence
[ E.length 3
, E.int 1
, AaaRecord.toCbor record
, TcpPort.toCbor tcpPort
]
Dns record ->
E.sequence
[ E.length 2
, E.int 2
, SrvRecord.toCbor record
]
Here, the usual tuple
helper would be hopeless since we can't commit
to a single constructor a priori. Using oneOf
is also unsatisfactory
as we could branch into the right decoder by just peaking at the first byte.
Using keep
and ignore
we can write an efficient decoder as such:
fromCbor : D.Decoder Hostname
fromCbor =
D.length
|> D.andThen (\len -> D.map (\tag -> ( len, tag )) D.int)
|> D.andThen
(\( _, tag ) ->
case tag of
0 ->
succeed Single
|> keep Ipv4.fromCbor
|> keep TcpPort.fromCbor
1 ->
succeed Multi
|> keep AaaRecord.fromCbor
|> keep TcpPort.fromCbor
2 ->
succeed Dns
|> SrvRecord.fromCbor
_ ->
D.fail
)
ignore : Decoder ignore -> Decoder keep -> Decoder keep
Ignore a field when manually decoding a structure. See keep
for
details
beginString : Decoder ()
Decode the beginning of an indefinite String
sequence.
This is useful in combination with ignoreThen
and
andThen
to manually decode sequences of text strings.
beginBytes : Decoder ()
Decode the beginning of an indefinite Bytes
sequence.
This is useful in combination with ignoreThen
and
andThen
to manually decode sequences of byte strings.
beginList : Decoder ()
Decode the beginning of an indefinite List
.
This is useful in combination with ignoreThen
and
andThen
to manually decode indefinite List
.
beginDict : Decoder ()
Decode the beginning of an indefinite Dict
.
This is useful in combination with ignoreThen
and
andThen
to manually decode indefinite Dict
.
break : Decoder ()
Decode a termination of an indefinite structure. See
beginString
, beginBytes
,
beginList
, beginDict
for detail about usage.
tag : Decoder Cbor.Tag.Tag
This implementation does little interpretation of the tags and is limited to only decoding the tag's value. The tag's payload has to be specified as any other decoder. So, using the previous example, one could decode a bignum as:
D.decode (D.tag |> D.andThen (\tag -> D.bytes)) input
You may also use tagged
if you as a helper to decode a tagged value, while
verifying that the tag matches what you expect.
D.decode (D.tagged PositiveBigNum D.bytes) input
tagged : Cbor.Tag.Tag -> Decoder a -> Decoder ( Cbor.Tag.Tag, a )
Decode a value that is tagged with the given Tag
.
Fails if the value is not tagged, or, tagged with some other Tag
D.decode (D.tagged Cbor D.int) Bytes<0xD8, 0x0E> == Just ( Cbor, 14 )
D.decode (D.tagged Base64 D.int) Bytes<0xD8, 0x0E> == Nothing
D.decode (D.tagged Cbor D.int) Bytes<0x0E> == Nothing
any : Decoder CborItem
Decode remaining bytes as any CborItem
. This is useful
for debugging or to inspect some unknown Cbor data.
D.decode D.any <| E.encode (E.int 14) == Just (CborInt 14)
raw : Decoder Bytes
Decode the next cbor item as a raw sequence of Bytes
.
Note that this primitive is innefficient since it needs to parse the underlying
CBOR first, and re-encode it into a sequence afterwards. Use for debugging
purpose only.