Reference
- Parsing
- Writing
- Flavors
- Custom parsers and writers
- Custom Iterators
- Convenience Iterators
- CborReader Helper Procedures
- CborWriter Helper Procedures
- Enums
This page provides an overview of the cbor_serialization API - for details, see the
API reference.
Parsing
Common API
CBOR parsing uses the common serialization API, supporting both object-based and dynamic CBOR data item:
type
NimServer = object
name: string
port: int
MixedServer = object
name: CborValueRef
port: int
RawServer = object
name: CborBytes
port: CborBytes
let rawCbor = Cbor.encode(NimServer(name: "localhost", port: 42))
# decode into native Nim
let native = Cbor.decode(rawCbor, NimServer)
# decode into mixed Nim + CborValueRef
let mixed = Cbor.decode(rawCbor, MixedServer)
# decode any value into nested cbor raw
let raw = Cbor.decode(rawCbor, RawServer)
# decode any valid CBOR, using the `cbor_serialization` node type
let value = Cbor.decode(rawCbor, CborValueRef)
Standalone Reader
A reader can be created from any faststreams-compatible stream:
var reader = Cbor.Reader.init(memoryInput(rawCbor))
let native2 = reader.readValue(NimServer)
# Overwrite an existing instance
var reader2 = Cbor.Reader.init(memoryInput(rawCbor))
var native3: NimServer
reader2.readValue(native3)
Parser options
Parser options allow you to control the limits of the parser. Set them by passing to Cbor.decode or when initializing the reader:
rawCbor, NimServer, conf = defaultCborReaderConf(nestedDepthLimit: 0))
Flavors can be used to override the defaults for some these options.
Limits
Parser limits are passed to decode, similar to flags:
You can adjust these defaults to suit your needs:
- nestedDepthLimit [=512]: Maximum nesting depth for objects and arrays (0 = unlimited).
- arrayElementsLimit [=0]: Maximum number of array elements (0 = unlimited).
- objectFieldsLimit [=0]: Maximum number of key-value pairs in an object (0 = unlimited).
- stringLengthLimit [=0]: Maximum string length in bytes (0 = unlimited).
- byteStringLengthLimit [=0]: Maximum byte string length in bytes (0 = unlimited).
- bigNumBytesLimit [=64]: Maximum number of BigNum bytes (0 = unlimited).
Special types
- CborBytes: Holds a CBOR value as a distinct
seq[byte]. - CborVoid: Skips a valid CBOR value.
- CborNumber: Holds a CBOR number.
- Use
toInt(n: CborNumber, T: type SomeInteger): Opt[T]to convert it to an integer. - The
integerfield for negative numbers is set toabs(value)-1as per the CBOR spec. This allows to hold a negativeuint64.highvalue.
- Use
- CborValueRef: Holds any valid CBOR value, it uses
CborNumberinstead ofint.
Writing
Common API
Similar to parsing, the common serialization API is used to produce CBOR data items.
# Convert object to cbor raw
let blob = Cbor.encode(native)
Standalone Writer
var output = memoryOutput()
var writer = Cbor.Writer.init(output)
writer.writeValue(native)
let decoded = Cbor.decode(output.getOutput(seq[byte]), NimServer)
echo decoded
Flavors
Flags and limits are runtime configurations, while a flavor is a compile-time mechanism to prevent conflicts between custom serializers for the same type. For example, a CBOR-RPC-based API might require that numbers are formatted as hex strings while the same type exposed through REST should use a number.
Flavors ensure the compiler selects the correct serializer for each subsystem. Use defaultSerialization to assign serializers of a flavor to a specific type.
# Parameters for `createCborFlavor`:
FlavorName: untyped
mimeTypeValue = "application/cbor"
automaticObjectSerialization = false
automaticPrimitivesSerialization = true
requireAllFields = true
omitOptionalFields = true
allowUnknownFields = true
skipNullFields = false
type
OptionalFields = object
one: Opt[string]
two: Option[int]
createCborFlavor OptCbor
OptCbor.defaultSerialization OptionalFields
automaticObjectSerialization: enable automatic serialization for all object types.automaticPrimitivesSerialization: enable automatic serialization for all primitive types.allowUnknownFields: Skip unknown fields instead of raising an error.requireAllFields: Raise an error if any required field is missing.omitOptionalFields: Writer ignores fields with null values.skipNullFields: Reader ignores fields with null values.
Custom parsers and writers
Parsing and writing can be customized by providing overloads for the readValue and writeValue functions. Overrides are commonly used with a flavor that prevents automatic serialization, to avoid that some types use the default serialization, should an import be forgotten.
# Custom serializers for MyType should match the following signatures
proc readValue*(r: var Cbor.Reader, v: var MyType) {.raises: [IOError, SerializationError].}
proc writeValue*(w: var Cbor.Writer, v: MyType) {.raises: [IOError].}
# When flavors are used, use the flavor reader/writer instead
proc readValue*(r: var MyFlavor.Reader, v: var MyType) {.raises: [IOError, SerializationError].}
proc writeValue*(w: var MyFlavor.Writer, v: MyType) {.raises: [IOError].}
Objects
Decode objects using the parseObject template. To parse values, use helper functions or readValue. The readObject and readObjectFields iterators are also useful for custom object parsers.
proc readValue*(r: var Cbor.Reader, table: var Table[string, int]) =
parseObject(r, key):
table[key] = r.parseInt(int)
Sets and List-like Types
Sets and list/array-like structures can be parsed using the parseArray template, which supports both indexed and non-indexed forms.
Built-in readValue implementations exist for regular seq and array. For set or set-like types, you must provide your own implementation.
type
HoldArray = object
data: array[3, int]
HoldSeq = object
data: seq[int]
WelderFlag = enum
TIG
MIG
MMA
Welder = object
flags: set[WelderFlag]
proc readValue*(r: var Cbor.Reader, value: var HoldArray) =
# parseArray with index, `i` can be any valid identifier
r.parseArray(i):
value.data[i] = r.parseInt(int)
proc readValue*(r: var Cbor.Reader, value: var HoldSeq) =
# parseArray without index
r.parseArray:
let lastPos = value.data.len
value.data.setLen(lastPos + 1)
readValue(r, value.data[lastPos])
proc readValue*(r: var Cbor.Reader, value: var Welder) =
# populating set also okay
r.parseArray:
value.flags.incl r.parseInt(int).WelderFlag
Custom Iterators
Custom iterators provide access to sub-token elements:
customIntValueIt(r: var CborReader; body: untyped)
customNumberValueIt(r: var CborReader; body: untyped)
customStringValueIt(r: var CborReader; limit: untyped; body: untyped)
customStringValueIt(r: var CborReader; body: untyped)
Convenience Iterators
readArray(r: var CborReader, ElemType: typedesc): ElemType
readObjectFields(r: var CborReader, KeyType: type): KeyType
readObjectFields(r: var CborReader): string
readObject(r: var CborReader, KeyType: type, ValueType: type): (KeyType, ValueType)
CborReader Helper Procedures
See the API reference
CborWriter Helper Procedures
See the API reference
Enums
type
Fruit = enum
Apple = "Apple"
Banana = "Banana"
Drawer = enum
One
Two
Number = enum
Three = 3
Four = 4
Mixed = enum
Six = 6
Seven = "Seven"
cbor_serialization automatically detects the expected representation for each enum based on its declaration.
Fruitexpects string literals.DrawerandNumberexpect numeric literals.Mixed(with both string and numeric values) is disallowed by default. If the CBOR value does not match the expected style, an exception is raised. You can configure individual enum types:
configureCborDeserialization(
T: type[enum], allowNumericRepr: static[bool] = false,
stringNormalizer: static[proc(s: string): string] = strictNormalize)
# Example:
Mixed.configureCborDeserialization(allowNumericRepr = true) # Only at top level
You can also configure enum encoding at the flavor or type level:
type
EnumRepresentation* = enum
EnumAsString
EnumAsNumber
EnumAsStringifiedNumber
# Examples:
# Flavor level
Cbor.flavorEnumRep(EnumAsString) # Default flavor, can be called from non-top level
Flavor.flavorEnumRep(EnumAsNumber) # Custom flavor, can be called from non-top level
# Individual enum type, regardless of flavor
Fruit.configureCborSerialization(EnumAsNumber) # Only at top level
# Individual enum type for a specific flavor
MyCbor.flavorEnumRep(Drawer, EnumAsString) # Only at top level