2020-05-30 12:37:12 +02:00
|
|
|
# Crystal CBOR
|
2020-04-17 14:32:43 +02:00
|
|
|
|
2020-04-19 13:55:23 +02:00
|
|
|
[![builds.sr.ht status](https://builds.sr.ht/~arestifo/crystal-cbor.svg)](https://builds.sr.ht/~arestifo/crystal-cbor?)
|
|
|
|
|
2020-04-17 14:32:43 +02:00
|
|
|
This library implements the [RFC7049: Concise Binary Object Representation (CBOR)][rfc]
|
|
|
|
in Crystal.
|
|
|
|
|
2020-05-30 12:37:12 +02:00
|
|
|
- [Installation](#installation)
|
|
|
|
- [Usage](#usage)
|
|
|
|
- [Supported Tags](#supported-tags)
|
|
|
|
- [Limitations](#limitations)
|
|
|
|
- [Community](#community)
|
|
|
|
- [Contributing](#contributing)
|
|
|
|
|
2020-04-23 09:41:46 +02:00
|
|
|
## Features
|
|
|
|
|
2020-05-30 12:37:12 +02:00
|
|
|
- Full RFC 7049
|
|
|
|
- Tested against all examples in the RFC 7049
|
|
|
|
- Simple and powerful API inspired by the standard library JSON
|
2020-04-23 09:41:46 +02:00
|
|
|
- Full support for diagnostic notation
|
2020-05-30 12:37:12 +02:00
|
|
|
- Support for a [wide range of IANA CBOR Tags](#supported-tags)
|
|
|
|
- Support custom CBOR Tags
|
|
|
|
|
|
|
|
## Example
|
|
|
|
|
|
|
|
```crystal
|
|
|
|
require "cbor"
|
|
|
|
|
|
|
|
class Location
|
|
|
|
include CBOR::Serializable
|
|
|
|
|
|
|
|
@[CBOR::Field(key: "lat")]
|
|
|
|
property latitude : Float64
|
|
|
|
|
|
|
|
@[CBOR::Field(key: "lng")]
|
|
|
|
property longitude : Float64
|
|
|
|
end
|
|
|
|
|
|
|
|
class House
|
|
|
|
include CBOR::Serializable
|
|
|
|
|
|
|
|
property address : String
|
|
|
|
property location : Location?
|
|
|
|
end
|
|
|
|
|
|
|
|
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[...]
|
2020-11-10 20:14:50 +01:00
|
|
|
CBOR::Diagnostic.to_s(bytes) # => {"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}
|
2020-05-30 12:37:12 +02:00
|
|
|
|
|
|
|
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[...]
|
2020-11-10 20:14:50 +01:00
|
|
|
CBOR::Diagnostic.to_s(bytes) # => [{"address": "Crystal Road 1234", "location": {"lat": 12.3, "lng": 34.5}}]
|
2020-05-30 12:37:12 +02:00
|
|
|
```
|
2020-04-23 09:41:46 +02:00
|
|
|
|
2020-04-17 14:32:43 +02:00
|
|
|
## Installation
|
|
|
|
|
|
|
|
1. Add the dependency to your `shard.yml`:
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
dependencies:
|
|
|
|
cbor:
|
2020-04-19 14:05:27 +02:00
|
|
|
git: https://git.sr.ht/~arestifo/crystal-cbor
|
2020-04-17 14:32:43 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
2. Run `shards install`
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
2020-05-30 12:37:12 +02:00
|
|
|
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.
|
|
|
|
|
2020-04-17 14:32:43 +02:00
|
|
|
```crystal
|
|
|
|
require "cbor"
|
2020-05-30 12:37:12 +02:00
|
|
|
|
|
|
|
class A
|
|
|
|
include CBOR::Serializable
|
|
|
|
|
|
|
|
@[CBOR::Field(key: "my_key")]
|
|
|
|
getter a : Int32?
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
### `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:
|
|
|
|
|
2020-06-01 23:12:05 +02:00
|
|
|
```crystal
|
2020-05-30 12:37:12 +02:00
|
|
|
require "cbor"
|
|
|
|
|
|
|
|
struct A
|
|
|
|
include CBOR::Serializable
|
|
|
|
@a : Int32
|
|
|
|
@b : Float64 = 1.0
|
|
|
|
end
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2020-06-01 23:12:05 +02:00
|
|
|
```crystal
|
2020-05-30 12:37:12 +02:00
|
|
|
require "cbor"
|
|
|
|
|
|
|
|
struct A
|
|
|
|
include CBOR::Serializable
|
|
|
|
include CBOR::Serializable::Unmapped
|
|
|
|
@a : Int32
|
|
|
|
end
|
|
|
|
|
|
|
|
a = A.from_cbor({"a" => 1, "b" => 2}.to_cbor) # => A(@cbor_unmapped={"b" => 2}, @a=1)
|
|
|
|
bytes = a.to_cbor # => Bytes[...]
|
2020-11-10 20:14:50 +01:00
|
|
|
CBOR::Diagnostic.to_s(bytes) # => {"a": 1, "b": 2}
|
2020-04-17 14:32:43 +02:00
|
|
|
```
|
|
|
|
|
2020-05-30 12:37:12 +02:00
|
|
|
### 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`)
|
|
|
|
|
2020-06-01 23:12:05 +02:00
|
|
|
```crystal
|
|
|
|
require "cbor"
|
2020-05-30 12:37:12 +02:00
|
|
|
|
|
|
|
@[CBOR::Serializable::Options(emit_nulls: true)]
|
|
|
|
class A
|
2020-06-01 23:12:05 +02:00
|
|
|
include CBOR::Serializable
|
2020-05-30 12:37:12 +02:00
|
|
|
@a : Int32?
|
|
|
|
end
|
|
|
|
```
|
2020-04-17 14:32:43 +02:00
|
|
|
|
2020-05-03 13:39:06 +02:00
|
|
|
## Supported tags
|
|
|
|
|
|
|
|
All the tags specified in [section 2.4 of RFC 7049][rfc-tags] are supported
|
|
|
|
and the values are encoded in the respective Crystal types:
|
|
|
|
|
|
|
|
- `Time`
|
|
|
|
- `BigInt`
|
2020-05-08 11:40:18 +02:00
|
|
|
- `BigDecimal`
|
2020-05-03 13:39:06 +02:00
|
|
|
|
2020-05-08 11:40:26 +02:00
|
|
|
## Limitations
|
|
|
|
|
|
|
|
### 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`.
|
|
|
|
|
2020-05-30 12:37:12 +02:00
|
|
|
## Community
|
2020-04-17 14:32:43 +02:00
|
|
|
|
2020-06-01 23:12:05 +02:00
|
|
|
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][mailing-list].
|
|
|
|
|
|
|
|
If you found an issue, you can [open an issue on the ticket tracker][tickets].
|
2020-04-17 14:32:43 +02:00
|
|
|
|
|
|
|
## Contributing
|
|
|
|
|
2020-05-08 11:40:26 +02:00
|
|
|
The code is hosted on SourceHut and the development happens over the
|
|
|
|
[crystal-cbor mailing list][mailing-list].
|
2020-04-17 14:32:43 +02:00
|
|
|
|
2020-05-08 11:43:41 +02:00
|
|
|
- For issues and feature requests, you can [open and issue in the ticket tracker][tickets].
|
|
|
|
|
|
|
|
- For code contributions You can send a patch to: [~arestifo/crystal-cbor@lists.sr.ht](mailto:~arestifo/crystal-cbor@lists.sr.ht).
|
2020-04-17 14:32:43 +02:00
|
|
|
|
2020-05-08 11:40:26 +02:00
|
|
|
To learn how to use `git send-email`, there is a great step-by-step tutorial
|
|
|
|
at [git-send-email.io](https://git-send-email.io/).
|
|
|
|
You might also want to read the [mailing list etiquette](https://man.sr.ht/lists.sr.ht/etiquette.md).
|
2020-04-17 14:32:43 +02:00
|
|
|
|
|
|
|
[rfc]: https://tools.ietf.org/html/rfc7049
|
2020-05-03 13:39:06 +02:00
|
|
|
[rfc-tags]: https://tools.ietf.org/html/rfc7049#section-2.4
|
2020-05-08 11:40:26 +02:00
|
|
|
[mailing-list]: https://lists.sr.ht/~arestifo/crystal-cbor
|
2020-05-08 11:43:41 +02:00
|
|
|
[tickets]: https://todo.sr.ht/~arestifo/crystal-cbor
|