{"id":48964323,"url":"https://github.com/spotflow-io/cbor-dotnet","last_synced_at":"2026-04-18T03:32:37.400Z","repository":{"id":322476308,"uuid":"1089568464","full_name":"spotflow-io/cbor-dotnet","owner":"spotflow-io","description":"High-performance serialization and deserialization of CBOR (Concise Binary Object Representation) data format from/to .NET objects and other types.","archived":false,"fork":false,"pushed_at":"2025-11-26T15:15:55.000Z","size":161,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-28T18:16:24.012Z","etag":null,"topics":["binary","c-sharp","cbor","deserialization","dotnet","formats","serialization","spotflow"],"latest_commit_sha":null,"homepage":"https://spotflow.io","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spotflow-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-04T14:16:58.000Z","updated_at":"2025-11-26T15:15:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/spotflow-io/cbor-dotnet","commit_stats":null,"previous_names":["spotflow-io/cbor-dotnet"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/spotflow-io/cbor-dotnet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotflow-io%2Fcbor-dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotflow-io%2Fcbor-dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotflow-io%2Fcbor-dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotflow-io%2Fcbor-dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spotflow-io","download_url":"https://codeload.github.com/spotflow-io/cbor-dotnet/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotflow-io%2Fcbor-dotnet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31955712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["binary","c-sharp","cbor","deserialization","dotnet","formats","serialization","spotflow"],"created_at":"2026-04-18T03:32:36.756Z","updated_at":"2026-04-18T03:32:37.391Z","avatar_url":"https://github.com/spotflow-io.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spotflow CBOR.NET\n\nA high-performance .NET library for serializing and deserializing CBOR (Concise Binary Object Representation) data. Built on top of `System.Formats.Cbor`, this library provides a simple, type-safe API similar to `System.Text.Json` for working with CBOR data.\n\n[![NuGet](https://img.shields.io/nuget/v/Spotflow.Cbor.svg)](https://www.nuget.org/packages/Spotflow.Cbor) ![CI status](https://github.com/spotflow-io/cbor-dotnet/actions/workflows/ci.yml/badge.svg?branch=main) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n\u003cp align=\"left\"\u003e\n  \u003cimg src=\"logo.png\" alt=\"Spotflow.Cbor Logo\" width=\"300\"/\u003e\n\u003c/p\u003e\n\n## Features\n\n- Type-safe API with support for generic types.\n- Built on top of the [`System.Formats.Cbor`](https://learn.microsoft.com/en-us/dotnet/api/system.formats.cbor).\n- Interface and behavior modeled after [`System.Text.Json`](https://learn.microsoft.com/en-us/dotnet/api/system.text.json).\n- Support for custom converters.\n- Strong nullability support with nullable reference types.\n- Support for `required` property modifier.\n- Flexible configuration options.\n- High-performance serialization and deserialization.\n- Built for .NET 8, .NET 9, and .NET 10.\n\n## Installation\n\n\n```bash\ndotnet add package Spotflow.Cbor\n```\n\n## Quick Start\n\n### Basic Serialization\n\n```csharp\nusing Spotflow.Cbor;\n\n// Serialize an object to CBOR\nvar person = new Person { Name = \"John\", Age = 30 };\nbyte[] cbor = CborSerializer.Serialize(person);\n\n// Deserialize CBOR back to an object\nvar deserializedPerson = CborSerializer.Deserialize\u003cPerson\u003e(cbor);\n```\n\n### With Options\n\n```csharp\nvar options = new CborSerializerOptions\n{\n    DefaultIgnoreCondition = CborIgnoreCondition.WhenWritingNull,\n    PropertyNamingPolicy = CborNamingPolicy.CamelCase,\n};\n\nbyte[] cbor = CborSerializer.Serialize(person, options);\n\nvar result = CborSerializer.Deserialize\u003cPerson\u003e(cbor, options);\n```\n\n\u003e [!IMPORTANT]\n\u003e **Reuse options instances for optimal performance.** Creating `CborSerializerOptions` is expensive as it initializes object pools and reflection-extracted information are heavily cached for each options instance. Create options once (e.g., as a static readonly field or property) and reuse them across multiple serialization calls.\n\n\n### Use numeric property names for smaller payloads\n\nTo assign numeric property names, use the `CborPropertyAttribute`:\n\n```csharp\npublic class Person\n{\n    [CborProperty(NumericName = 1)]\n    public string Name { get; set; }\n    \n    [CborProperty(NumericName = 2)]\n    public int Age { get; set; }\n}\n\nvar options = new CborSerializerOptions\n{\n    PreferNumericPropertyNames = true // Default\n};\n\nvar person = new Person { Name = \"Alice\", Age = 25 };\n\n// Encoded CBOR contains only minimal numeric property names, instead the full text names.\n\nbyte[] cbor = CborSerializer.Serialize(person, options); \n```\n\n## Supported .NET Types\n\n### Primitive Types\n\n- **Integers**: `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `Int128`, `UInt128`\n- **Floating Point**: `Half`, `float`, `double`\n- **Boolean**: `bool`\n- **String**: `string`\n- **Bytes**: `byte[]`, `ReadOnlyMemory\u003cbyte\u003e`, `Memory\u003cbyte\u003e`.\n- **Big Integers**: `BigInteger`\n\n### Date and Time Types\n\n- `DateTime`, `DateTimeOffset` - Supports RFC3339/ISO 8601 text strings and Unix timestamps when reading. When writing, the text strings are used, optionally with a specific CBOR tag.\n- `DateOnly`, `TimeOnly` - Reading from .NET-specific text format (e.g., \"yyyy-MM-dd\" for `DateOnly` and \"HH:mm:ss.fffffff\" for `TimeOnly`) or from RFC3339/ISO 8601 text strings and Unix timestamps when specific CBOR tags are present when reading. When writing, the .NET-specific text formats are used.\n- `TimeSpan` - Reading and writing from a .NET-specific text format (\"d.hh:mm:ss.fffffff\") or from numbers representing seconds, possibly fractional.\n\n### Other Common Types\n\n- `Guid` - Reading from byte string or .NET specific text formats (when specific CBOR tags are not present). When writing, the byte string format is used.\n- `Uri` - Reading and writing absolute and relative URIs.\n- `Enum` - By default, enums are read and written as their numeric values. Optionally, their string representations can be used (by adding `CborStringEnumConverter`). When using string representations, custom names can be specified via the `CborStringEnumMemberNameAttribute`. See example below.\n\n### Collection Types\n\n**Lists and Arrays**:\n- `List\u003cT\u003e`\n- `IList\u003cT\u003e`\n- `IReadOnlyList\u003cT\u003e`\n- `ICollection\u003cT\u003e`\n- `IReadOnlyCollection\u003cT\u003e`\n- `IEnumerable\u003cT\u003e`\n- `T[]` (arrays)\n\n**Dictionaries**:\n- `Dictionary\u003cTKey, TValue\u003e`\n- `IDictionary\u003cTKey, TValue\u003e`\n- `IReadOnlyDictionary\u003cTKey, TValue\u003e`\n- `ConcurrentDictionary\u003cTKey, TValue\u003e`\n- `FrozenDictionary\u003cTKey, TValue\u003e`\n\n### Complex Types\n\n- **Custom Classes and Structs** - Serialized as CBOR maps\n- **Nested Objects** - Full support for deep object hierarchies\n- **Nullable Value Types** - `int?`, `DateTime?`, etc.\n- **Nullable Reference Types** - Proper null handling.\n\n### Type Handling Features\n\n- Respect for nullable annotations.\n- Deep nesting with configurable max depth.\n- Enum serialization as numbers or strings\n- Custom property naming with attributes (`CborPropertyAttribute`) and/or `PropertyNamingPolicy` option.\n- Optional case-insensitive property name matching.\n\n## Nullability and Required Properties\n\n### The `required` Modifier\n\nProperties marked with the `required` modifier must be present in the CBOR data during deserialization. If a required property is missing, a `CborSerializerException` is thrown:\n\n```csharp\npublic class Person\n{\n    public required string Name { get; init; }  // Must be present\n    public required int Age { get; init; }      // Must be present\n    public string? Nickname { get; init; }      // Optional\n}\n```\n**Important**: The `required` modifier is checked regardless of nullability. Both `required string Name` and `required string? Name` must be present in the CBOR data.\n\n### Reference Types\n\nThe `RespectNullableAnnotations` option controls how nullable reference type annotations (`string?` vs `string`) are handled:\n\n**When `RespectNullableAnnotations = false` (default)**:\n- Null values are allowed for all reference types, regardless of nullability annotations\n- `string` and `string?` are treated identically.\n- This matches the default behavior of most serializers.\n\n```csharp\npublic class Person\n{\n    public string Name { get; init; }   // Can be null\n    public string? Nickname { get; init; } // Can be null\n}\n```\n\n**When `RespectNullableAnnotations = true`**:\n- Non-nullable reference types (`string`) cannot be null.\n- Nullable reference types (`string?`) can be null.\n- Attempting to deserialize null into a non-nullable reference type throws `CborSerializerException`.\n- If a property is not marked as `required`, it is not deserialized if missing, effectively having a null value.\n\n```csharp\npublic class Person\n{\n    public string Name1 { get; init; }   // Cannot be deserialized from null, but is not required so the property can effectively have a null value.\n    public required string Name2 { get; init; } // Must be present and cannot be deserialized null.\n    public string? Name3 { get; init; } // Can be null or missing.\n    public required string? Name4 { get; init; } // Must be present, but can be deserialized from null.\n}\n```\n\n### Value Types\n\n- Null cannot be assigned to non-nullable value types such as `int` or `DateTime`.\n- Null can be assigned to nullable value types (e.g., `int?`, `DateTime?`).\n- If a non-nullable value type property is missing in the CBOR data, it will receive the default value of that type (e.g., `0` for `int`, `DateTime.MinValue` for `DateTime`). To enforce presence, use the `required` modifier.\n\n```csharp\npublic class Record\n{\n    public int Count1 { get; init; }      // Cannot be null but can be missing (default value 0 is assigned).\n    public required int Count2 { get; init; } // Must be present and cannot be null.\n    public int? Count3 { get; init; } // Can be null or missing.\n    public required int? Count4 { get; init; } // Must be present, but can be null.\n}\n```\n\n### Best Practices\n\n* **Use `required` for mandatory data**: Mark properties as `required` when they must always be present in your data model.\n* **Consider `RespectNullableAnnotations = true` for new projects**: This provides stronger type safety and better aligns with C# nullable reference types.\n* **Handle missing vs. null**: Remember that \"missing\" and \"null\" are different concepts in CBOR. Use `required` to enforce presence, and nullability to control whether null values are allowed.\n\n## CBOR serialization \u0026 deserialization\n\n- Definite-length encoding for objects, collections and dictionaries, when possible.\n- All numbers (including `BigInteger`) are encoded as a minimal CBOR numeric type.\n- CBOR tags are explicitly decoded and provided to converters.\n- CBOR self-describing tag (55799) support for both reading and writing.\n\n## Performance\n\nThe library is designed with performance in mind:\n\n* **Object Pooling**: `CborReader` and `CborWriter` instances are pooled and reused to minimize allocations.\n* **Converter Caching**: Type converters are cached using `ConcurrentDictionary` to avoid repeated reflection and converter resolution:\n   - Type-to-converter mappings\n   - Property-to-converter mappings\n   - Fallback converter cache\n   - Nullability type checks\n* **Zero reflection**: On subsequent serializations/deserializations for the same types when using the same `CborSerializerOptions` instance, using compiled [`System.Linq.Expressions`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions) delegates.\n* **Zero-allocation paths**: Where possible, using `Span\u003cT\u003e` and `stackalloc`.\n* **TrySerialize API**: Serialize directly into pre-allocated buffers to avoid intermediate allocations.\n\nOverall, the library performs a lot of work during the first serialization/deserialization with a specific `CborSerializerOptions` instance to optimize all subsequent calls with the same options. Therefore, it is not very suitable for scenarios where options can't be reused for multiple calls. \n\nLibrary is currently not used source generators, apart compiled [`System.Linq.Expressions`](https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions) delegates.\n\nExample with reusable options:\n\n```csharp\n// Configure once, reuse many times\nprivate static  CborSerializerOptions Options { get; } = new()\n{\n    DefaultIgnoreCondition = CborIgnoreCondition.WhenWritingNull,\n    MaxDepth = 32\n};\n\n// Fast subsequent calls due to cached converters and pooled readers/writers\nbyte[] cbor1 = CborSerializer.Serialize(obj1, Options);\nbyte[] cbor2 = CborSerializer.Serialize(obj2, Options);\n```\n\n## Configuration Options\n\n### Serialization Behavior\n\n**`DefaultIgnoreCondition`** - Controls when properties are ignored during serialization.\n\n- `CborIgnoreCondition.Never` (default) - Always serialize properties\n- `CborIgnoreCondition.WhenWritingNull` - Ignore properties with null values\n\n**`UnmappedMemberHandling`** - Specifies how to handle CBOR properties that don't map to .NET properties during deserialization.\n\n- `CborUnmappedMemberHandling.Skip` (default) - Ignore unmapped properties\n- `CborUnmappedMemberHandling.Throw` - Throw an exception when encountering unmapped properties\n\n**`MaxDepth`** - Maximum allowed depth for nested objects and collections. Default is `64` (or `CborSerializerOptions.DefaultMaxDepth`). Set to `0` to use the default.\n\n### Property Naming\n\n**`PropertyNamingPolicy`** - Defines the naming policy for property names. Default is `null` (use property names as-is).\n\n- `CborNamingPolicy.CamelCase` - Convert property names to camelCase\n\n**`PreferNumericPropertyNames`** - When `true` (default), uses numeric property names (defined via `[CborProperty(NumericName = ...)]`) instead of text names (if available) for smaller payload sizes.\n\n**`PropertyNameCaseInsensitive`** - When `true`, property name matching during deserialization is case-insensitive. Default is `false`.\n\n### Nullability\n\n**`RespectNullableAnnotations`** - When `true`, respects nullable reference type annotations (`string?` vs `string`). Default is `false`.\n\n**`HandleUndefinedValuesAsNulls`** - When `true`, treats CBOR undefined values (simple value 23) as null. Default is `false`.\n\n### Type Handling\n\n**`NumberHandling`** - Controls how numbers are read and written.\n\n- `CborNumberHandling.Strict` (default) - Numbers must be encoded as CBOR numbers.\n- `CborNumberHandling.AllowReadingFromString` - Allow reading numbers from strings.\n- `CborNumberHandling.WriteAsString` - Write numbers as strings.\n\nFlags can be combined: `NumberHandling = CborNumberHandling.AllowReadingFromString | CborNumberHandling.WriteAsString`\n\n**`BooleanHandling`** - Controls how booleans are read during deserialization. This is a flags enum that can be combined.\n\n- `CborBooleanHandling.Strict` (default) - Booleans can only be read from CBOR boolean tokens (major type 7)\n- `CborBooleanHandling.AllowReadingFromInteger` - Additionally allows reading booleans from integer tokens (0 for `false`, any other value for `true`)\n- `CborBooleanHandling.AllowReadingFromString` - Additionally allows reading booleans from string tokens (\"true\", \"false\"), case-insensitive\n\nFlags can be combined: `BooleanHandling = CborBooleanHandling.AllowReadingFromInteger | CborBooleanHandling.AllowReadingFromString`\n\n### CBOR Format\n\n**`ConformanceMode`** - Specifies the CBOR conformance mode.\n\n- `Strict` (default) - Strict RFC 8949 conformance\n- Other modes: `Lax`, `Canonical`, `Ctap2Canonical`\n\n**`ConvertIndefiniteLengthEncodings`** - When `true`, converts indefinite-length encodings to definite-length during writing. Default is `false`.\n\n### CBOR Tags\n\n**`WriteSelfDescribeTag`** - When `true`, writes the self-describe CBOR tag (55799) at the start of the output. Default is `false`.\n\n**`WriteDateTimeStringTag`** - When `true`, writes CBOR tag 0 before `DateTime` and `DateTimeOffset` values serialized as RFC3339 strings. Default is `false`.\n\n### Custom Converters\n\n**`Converters`** - A collection of custom `CborConverter` instances to use for serialization/deserialization. Add custom converters to this list to override default behavior for specific types.\n\n## Custom Attributes\n\n### Property Configuration\n\n```csharp\npublic class Person\n{\n    [CborProperty(NumericName = 1, TextName = \"custom_text_name\")]\n    public string Name { get; set; }\n    \n    [CborProperty(NumericName = 2)]\n    public int Age { get; set; }\n}\n```\n\n### Ignoring Properties\n\n```csharp\npublic class User\n{\n    public string Username { get; set; }\n    \n    [CborIgnore]  // Never serialized or deserialized\n    public string Password { get; set; }\n    \n    [CborIgnore(Condition = CborIgnoreCondition.WhenWritingNull)]  // Ignored only when null\n    public string? Bio { get; set; }\n}\n```\n\n### Enum Customization\n\n```csharp\npublic enum Status\n{\n    [CborStringEnumMemberName(\"active\")]\n    Active,\n    \n    [CborStringEnumMemberName(\"inactive\")]\n    Inactive\n}\n```\n\n## Custom Converters\n\nCreate custom converters by inheriting from `CborConverter\u003cT\u003e`:\n\n```csharp\npublic class CustomConverter : CborConverter\u003cMyType\u003e\n{\n    public override MyType Read(CborReader reader, Type typeToConvert, CborTag? tag, CborSerializerOptions options)\n    {\n        // Custom deserialization logic\n    }\n    \n    public override void Write(CborWriter writer, MyType value, CborSerializerOptions options)\n    {\n        // Custom serialization logic\n    }\n}\n\n// Register the converter\nvar options = new CborSerializerOptions();\noptions.Converters.Add(new CustomConverter());\n```\n\n## Error Handling\n\nThe library intentionally throws following exceptions:\n\n* `CborSerializerException` - For serialization/deserialization errors with detailed path information.\n* `NotSupportedException` - For unsupported types or operations.\n* `CborContentException` - Exception thrown by the underlying `CborReader` and `CborWriter` instances.\n* `FormatException` - For format-related issues.\n* `OverflowException` - For numeric overflows during conversion.\n\nAll of these exceptions are intercepted within the library, wrapped into a new exception with additional information (like the current CBOR path) appended to the message.\n\n## Advanced Features\n\n### Working with CborReader/CborWriter\n\nIf you need to combine `CborSerializer` with direct `CborReader` or `CborWriter` usage, you can pass your own instances of `CborReader` or `CborWriter` to the serializer. In this case, the instances will not be pooled.\n\n```csharp\nvar reader = new CborReader(cbor);\nvar result = CborSerializer.Deserialize\u003cMyType\u003e(reader, options);\n\nvar writer = new CborWriter();\nvar encoded = CborSerializer.Serialize(value, writer, options);\n```\n\n### Self-Describe Tag Detection\n\n```csharp\nif (CborSerializer.StartsWithSelfDescribeTag(cborData))\n{\n    // Handle self-described CBOR\n}\n```\n\n## Maintainers\n\n-   [Tomáš Pajurek](https://github.com/tomas-pajurek) ([Spotflow](https://spotflow.io))\n\n## Contributing\n\nPlease read our [Contributing Guidelines](./CONTRIBUTING.md) to learn how you can contribute to this project.\n\n## License\n\nThis project is licensed under the [MIT license](./LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspotflow-io%2Fcbor-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspotflow-io%2Fcbor-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspotflow-io%2Fcbor-dotnet/lists"}