CBOR encoding and decoding in Crystal

#2 Support for UUID tag

~arestifo commented on crystal-cbor todo

a month ago

#2 Support for UUID tag

~sorcus filed ticket on crystal-cbor todo

a month ago

#Crystal CBOR

builds.sr.ht status

This library implements the RFC7049: Concise Binary Object Representation (CBOR) in Crystal.


  • Full RFC 7049
  • Tested against all examples in the RFC 7049
  • Simple and powerful API inspired by the standard library JSON
  • Full support for diagnostic notation
  • Support for a wide range of IANA CBOR Tags
  • Support custom CBOR Tags


require "cbor"

class Location
  include CBOR::Serializable

  @[CBOR::Field(key: "lat")]
  property latitude : Float64

  @[CBOR::Field(key: "lng")]
  property longitude : Float64

class House
  include CBOR::Serializable

  property address : String
  property location : Location?

data = {
  "address" => "Crystal Road 1234",
  "location" => { "lat" => 12.3, "lng" => 34.5 }
cbor = data.to_cbor         # => Bytes[...]
CBOR::Diagnostic.to_s(cbor) # => {"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}

house = House.from_cbor(cbor)
house.address                 # => "Crystal Road 1234"
house.location                # => #<Location:0x10cd93d80 @latitude=12.3, @longitude=34.5>
bytes = house.to_cbor         # => Bytes[...]
CBOR::Diagnostic.to_s(bytes)  # => {"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}

data_array = [data]
cbor_array = data_array.to_cbor # => Bytes[...]
CBOR::Diagnostic.to_s(cbor)     # => [{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}]}

houses = Array(House).from_cbor(cbor_array)
houses.size                  # => 1
bytes = houses.to_cbor       # => Bytes[...]
CBOR::Diagnostic.to_s(bytes) # => [{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}]


  1. Add the dependency to your shard.yml:

        git: https://git.sr.ht/~arestifo/crystal-cbor
  2. Run shards install


Including CBOR::Serializable will create #to_cbor and self.from_cbor methods on the current class, and a constructor which takes a CBOR::Decoder.

By default, these methods serialize into a cbor map containing the value of every instance variable, the keys being the instance variable name.

Most primitives and collections are supported as instance variable values (string, integer, array, hash, etc.), along with objects which define to_cbor and a constructor taking a CBOR::Decoder.

Union types are also supported, including unions with nil. If multiple types in a union parse correctly, it is undefined which one will be chosen.

To change how individual instance variables are parsed and serialized, the annotation CBOR::Field can be placed on the instance variable. Annotating property, getter and setter macros is also allowed.

require "cbor"

class A
  include CBOR::Serializable

  @[CBOR::Field(key: "my_key")]
  getter a : Int32?

#CBOR::Field properties

  • ignore: if true skip this field in serialization and deserialization (by default false)
  • key: the value of the key in the json object (by default the name of the instance variable)
  • converter: specify an alternate type for parsing and generation. The converter must define from_cbor(CBOR::Decoder) and to_cbor(value, CBOR::Builder) as class methods. Examples of converters are Time::Format::RFC_333 and Time::EpochConverter for Time.
  • presence: if true, a @{{key}}_present instance variable will be generated when the key was present (even if it has a null value), false by default
  • emit_null: if true, emits a null value for nilable property (by default nulls are not emitted)
  • nil_as_undefined: if true, when the value is nil, it is emitted as undefined (by default nil are encoded as null)

Deserialization also respects default values of variables:

require "cbor"

struct A
  include CBOR::Serializable
  @a : Int32
  @b : Float64 = 1.0

A.from_cbor({"a" => 1}.to_cbor) # => A(@a=1, @b=1.0)

#Extensions: CBOR::Serializable::Unmapped

If the CBOR::Serializable::Unmapped module is included, unknown properties in the CBOR document will be stored in a Hash(String, CBOR::Type).

On serialization, any keys inside cbor_unmapped will be serialized and appended to the current cbor map.

require "cbor"

struct A
  include CBOR::Serializable
  include CBOR::Serializable::Unmapped
  @a : Int32

a = A.from_cbor({"a" => 1, "b" => 2}.to_cbor) # => A(@cbor_unmapped={"b" => 2}, @a=1)
bytes = a.to_cbor                             # => Bytes[...]
CBOR::Diagnostic.to_s(bytes)                  # => {"a": 1, "b": 2}

#Class annotation CBOR::Serializable::Options

supported properties:

  • emit_nulls: if true, emits a null value for all nilable properties (by default nulls are not emitted)
  • nil_as_undefined: if true, emits a nil value as undefined (by default nil emits null)
require "cbor"

@[CBOR::Serializable::Options(emit_nulls: true)]
class A
  include CBOR::Serializable
  @a : Int32?

#Supported tags

All the tags specified in section 2.4 of RFC 7049 are supported and the values are encoded in the respective Crystal types:

  • Time
  • BigInt
  • BigDecimal


#Maximum Array/String array/Bytes array length

The spec allows for the maximum length of arrays, string arrays and bytes array to be a UInt64.

While this library supports lengths expressed as a UInt64, it must not exceed Int32::MAX.


If you're stuck and need help, if you have any questions, or if you simply want to stay up to date with the latest news and developments, you can subscribe to the crystal-cbor mailing list.

If you found an issue, you can open an issue on the ticket tracker.


The code is hosted on SourceHut and the development happens over the crystal-cbor mailing list.

To learn how to use git send-email, there is a great step-by-step tutorial at git-send-email.io. You might also want to read the mailing list etiquette.