{"id":26336275,"url":"https://github.com/point-platform/dasher","last_synced_at":"2025-03-16T01:17:22.171Z","repository":{"id":66317872,"uuid":"47437837","full_name":"point-platform/dasher","owner":"point-platform","description":"Fast, lightweight, cross platform serialisation tool","archived":false,"fork":false,"pushed_at":"2017-12-19T09:48:08.000Z","size":766,"stargazers_count":8,"open_issues_count":5,"forks_count":6,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-11T10:41:45.886Z","etag":null,"topics":["deserialisation","dotnet","dotnet-core","messaging","schema","serialisation","union-types"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/point-platform.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-12-05T01:51:47.000Z","updated_at":"2025-01-18T01:23:58.000Z","dependencies_parsed_at":"2023-06-09T06:30:34.002Z","dependency_job_id":null,"html_url":"https://github.com/point-platform/dasher","commit_stats":null,"previous_names":["drewnoakes/dasher"],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/point-platform%2Fdasher","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/point-platform%2Fdasher/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/point-platform%2Fdasher/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/point-platform%2Fdasher/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/point-platform","download_url":"https://codeload.github.com/point-platform/dasher/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243809860,"owners_count":20351407,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["deserialisation","dotnet","dotnet-core","messaging","schema","serialisation","union-types"],"created_at":"2025-03-16T01:17:21.606Z","updated_at":"2025-03-16T01:17:22.157Z","avatar_url":"https://github.com/point-platform.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"![dasher logo](https://cdn.rawgit.com/drewnoakes/dasher/master/Resources/logo.svg)\n\n[![Build status](https://ci.appveyor.com/api/projects/status/s8rusi6ir56tnvoj?svg=true)](https://ci.appveyor.com/project/drewnoakes/dasher)\n[![Dasher NuGet version](https://img.shields.io/nuget/v/Dasher.svg)](https://www.nuget.org/packages/Dasher/)\n[![Dasher NuGet pre-release version](https://img.shields.io/nuget/vpre/Dasher.svg)](https://www.nuget.org/packages/Dasher/)\n\nDasher is a fast, lightweight, cross-platform serialisation tool.\n\nUse it to get data objects from one application to another, and to reliably control behaviour as contracts evolve independently.\n\n---\n\nIn inter-application communication, message contracts evolve and problematic incompatibilities can arise.\nIn the worst case, these failures are silent and untracked.\nDasher gives you control over what happens as versions change, in a way that's very natural to C# developers.\nYou can be very strict about what you receive, or lenient.\n\n# API\n\nThe core API is very simple:\n\n```csharp\n// serialise to stream\nnew Serialiser\u003cHoliday\u003e().Serialise(stream, christmas);\n\n// deserialise from stream\nvar christmas = new Deserialiser\u003cHoliday\u003e().Deserialise(stream);\n```\n\nDasher messages are regular CLR types (POCO), with no need for a base class or attributes. They can (and should) be immutable as well:\n\n```csharp\npublic sealed class Holiday\n{\n    public string Name { get; }\n    public DateTime Date { get; }\n\n    public Holiday(string name, DateTime date)\n    {\n        Name = name;\n        Date = date;\n    }\n}\n```\n\n# Supported types\n\nBoth serialiser and deserialiser support the core built-in types of `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`,\n`ulong`, `float`, `double`, `decimal`, `string`, `char`, as well as `byte[]`, `ArraySegment\u003cbyte\u003e`, `DateTime`, `DateTimeOffset`, `TimeSpan`, `Guid`, `IntPtr`,\n`Version`, `Nullable\u003cT\u003e`, `IReadOnlyList\u003cT\u003e`, `IReadOnlyDictionary\u003cTKey, TValue\u003e`, `Tuple\u003c...\u003e`, `ValueTuple\u003c...\u003e` and enum types.\n\nDasher also provides and supports types:\n\n- `Dasher.Union\u003c...\u003e` allows a value to be one of a fixed number of known types\n- `Dasher.Empty` represents a value with no fields or state that may gracefully support addition of fields in future versions\n\nMore details on `Union` and `Empty` types can be found later in this document.\n\nTypes not listed so far are treated as _complex types_. Complex types have their properties serialised, and are deserialised\naccording to their constructor parameters.\n\nIf custom treatment for specific types is required, the `ITypeProvider` interface allows bespoke code to be emitted for\nserialisation and deserialisation at runtime.\n\n---\n\n# Contract Evolution\n\nOver time, the serialiser and/or deserialiser may need to modify the contracts of the messages they exchange.\n\nDasher gives the receiver the final say in whether it will accept a given message or not.\nTherefore, Dasher's `Deserialiser` class governs, through user configuration, how different messages are interpreted.\n\nLet's look at some examples.\n\n## Handing unexpected fields\n\nThe most common modifications to contracts are the addition and removal of fields.\nIn this example, the serialised message contains a field that the deserialiser does not expect.\n\nThis may be because the serialiser added the field, or because the deserialiser removed it. In practice, both situations are the same.\n\nImagine a deserialiser for this class:\n\n```csharp\npublic sealed class Reindeer\n{\n    public string Name { get; }\n    public int Position { get; }\n\n    public Reindeer(string name, int position)\n    {\n        Name = name;\n        Position = position;\n    }\n}\n```\n\nConsider the payload to deserialise as:\n\n```json\n{ \"Name\": \"Dasher\", \"Position\": 1, \"HasRedNose\": false }\n```\n\nClearly `HasRedNose` is an unexpected field. There are two possible behaviours: ignore the field, or throw an exception. Which occurs depends upon how the `Deserialiser` is instantiated.\n\n```csharp\n// return new Reindeer(\"Dasher\", 1)\nnew Deserialiser\u003cReindeer\u003e(UnexpectedFieldBehaviour.Ignore).Deserialise(...);\n\n// throw new DeserialisationException(\"Unexpected field \\\"HasRedNose\\\".\", typeof(Reindeer))\nnew Deserialiser\u003cReindeer\u003e(UnexpectedFieldBehaviour.Throw).Deserialise(...);\n```\n\nWhen no `UnexpectedFieldBehaviour` is specified in the deserialiser's constructor, the default behaviour is to throw:\n\n```csharp\n// throws as above\nnew Deserialiser\u003cReindeer\u003e().Deserialise(...);\n```\n\n## Handling missing fields\n\nIn the previous example we considered the case where the serialiser provided a field that was not expected by the deserialiser. Now we consider the opposite case, where the deserialiser requires a field that the serialiser has not provided.\n\nThis may be because the deserialiser added the field, or because the serialiser removed it. In practice, both situations can be treated in the same way.\n\n### Missing a required field\n\n```csharp\npublic sealed class Reindeer\n{\n    public string Name { get; }\n    public int Position { get; }\n\n    public Reindeer(string name, int position)\n    {\n        Name = name;\n        Position = position;\n    }\n}\n```\n\nConsider the payload to deserialise as:\n\n```json\n{ \"Name\": \"Dancer\" }\n```\n\nThe `Position` field is not specified, and the programmer who declared the `Reindeer` type requires such a field to be passed to the constructor.\n\nIn this case, attempts to deserialise the payload result in an exception:\n\n```csharp\n// throw new DeserialisationException(\"Missing required field \\\"Position\\\".\", typeof(Reindeer))\nnew Deserialiser\u003cReindeer\u003e().Deserialise(...);\n```\n\n### Missing an optional field\n\nOften it's desirable to make a field optional by providing a default value to use when no value is specified. This enables backwards-compatible modifications to contracts, allowing serialisers to be updated independently of deserialisers, at some later time.\n\n```csharp\npublic sealed class Reindeer\n{\n    public string Name { get; }\n    public int Position { get; }\n    public bool HasRedNose { get; }\n\n    public Reindeer(string name, int position, bool hasRedNose = false)\n    {\n        Name = name;\n        Position = position;\n        HasRedNose = hasRedNose;\n    }\n}\n```\nConsider the payload to deserialise as:\n\n```json\n{ \"Name\": \"Prancer\", \"Position\": 3 }\n```\n\nNo value is provided for `HasRedNose` in the message, however the constructor parameter includes a default value of `false`. In such a case, Dasher uses the default value of a field when the field is omitted from the payload.\n\n```csharp\n// return new Reindeer(\"Prancer\", 3, false)\nnew Deserialiser\u003cReindeer\u003e(UnexpectedFieldBehaviour.Ignore).Deserialise(...);\n```\n\n---\n\n# Union Types\n\nDasher is strict about the types it deals with. This allows great control over message contract versions, but sometime you don't know exactly which type you will need. Union types allow flexibility, but in a controlled fashion.\n\nDasher provides a `Union\u003cT1,T2,...\u003e` type. This allows a given field's type to be one of a set of known types.\n\nLet's look at some examples. Firstly, construction of a union instance:\n\n```csharp\n// implicit conversion\nUnion\u003cint, string\u003e union1 = 1;\nUnion\u003cint, string\u003e union2 = \"Hello\";\n\n// alternatively, explicit construction\nvar union1 = Union\u003cint, string\u003e.Create(1);\nvar union2 = Union\u003cint, string\u003e.Create(\"Hello\");\n```\n\nThere are a few ways to consume unions:\n\n```csharp\n// consuming a union\nunion1.Type   // int\nunion1.Value  // 1 (boxed as object)\n\n// perform an action based on type\nunion1.Match(\n  i =\u003e Console.WriteLine($\"Union holds an int: {i}\"),\n  s =\u003e Console.WriteLine($\"Union holds a string: {s}\"));\n\n// mapping based on type\nvar num = union1.Match(\n  i =\u003e i * 2,\n  s =\u003e s.Length);\n```\n\nThe `Match` methods offer great performance as they won't box value types and don't involve any lookup based on type (beyond a single vtable lookup).\n\nA class could have a property of type `Union\u003cint, string\u003e` and be successfully serialised and deserialised by Dasher. That property can contain either of these types.\n\nThis allows a controlled form of polymorphism (with base type requirement), and allows for heterogeneous lists/dictionaries. For example:\n\n```csharp\nIReadOnlyList\u003cUnion\u003cAddItemRequest, RemoveItemRequest\u003e\u003e Requests { get; }\n```\n\nUnions are fully composable, so the above to could be inverted if you knew all items in the list would have the same type:\n\n```csharp\nUnion\u003cIReadOnlyList\u003cAddItemRequest\u003e, IReadOnlyList\u003cRemoveItemRequest\u003e\u003e Requests { get; }\n```\n\n---\n\n# Empty Type\n\n`Empty` is useful in contracts when a value is not currently required, but may be one day in future.\n\nConsider a message that indicates some kind of notification. The message itself might not contain any fields,\nas it is enough to simply observe the empty message. However one day, you may wish to add one or more optional\nfields and support backwards compatibility. In such a case, use `Empty` today and introduce a complex or union\ntype at some later point.\n\nIt is expected that `Empty` would be most useful as a top-level type (i.e. `Serialiser\u003cEmpty\u003e`) or used in\nconjunction with generic wrapper types (e.g. `Serialiser\u003cMyEnvelope\u003cEmpty\u003e\u003e\u003e`).\n\nThe `Dasher.Empty` CLR type itself cannot be instantiated or subclassed. At runtime, the value will be `null`.\n\nEmpty values may be deserialised to the following types:\n\n- A complex type for which all constructor parameters have default values, where an instance is instantiated with those defaults\n- A union type, where the resulting value is `null`\n- A nullable nullable complex struct, where the resulting value is `null`\n\nIf a `Deserialiser` is constructed with option `UnexpectedFieldBehaviour.Ignore`, then non-empty values received for an `Empty` type\nare discarded. However if `UnexpectedFieldBehaviour.Throw` is used (the default setting) then a `DeserialisationException` is raised.\n\n---\n\n# Encoding\n\nYou don't _need_ to know how Dasher encodes messages in order to use it successfully, but a deeper understanding is never a bad thing.\nThankfully it's not too complex.\n\nOn the wire, messages are encoded using [MsgPack](http://msgpack.org), which is an efficient binary serialisation format.\nIf you're familiar with JSON, you can think of it in much the same way.\nMsgPack messages are quick to pack and unpack, and the resulting byte stream is very concise (much more so than JSON or XML for example).\n\nUsing our example from above, this object:\n\n```csharp\nnew Holiday(\"Christmas\", new DateTime(2015, 12, 25));\n```\n\nWould logically be encoded as (using JSON for readability):\n\n```json\n{ \"Name\": \"Christmas\", \"Date\": 635865984000000000 }\n```\n\nAnd physically encoded as (the actual byte stream):\n\n```plain\n82 A4 4E 61 6D 65 A9 43 68 72 69 73 74 6D 61 73 A4 44 61 74 65 D3 08 D3 0C BE 55 13 00 00\n|  |___________/  |__________________________/  |___________/  |_______________________/\n|  |              |                             |              |\n|  \\ \"Name\"       \\ \"Christmas\"                 \\ \"Date\"       \\ 64-bit integer\n|\n\\ map of two key/value pairs\n```\n\nMsgPack specifies the encoding of types `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `bool`, `string`, and `byte[]`.\n\nDasher encodes the following types using the following MsgPack formats:\n\n* Array (other than `byte[]`) as array\n* `ArraySegment\u003cbyte\u003e` as binary (byte array)\n* `char` as 1-character string\n* `DateTime` as 64-bit integer\n* `DateTimeOffset` as two-element array of 64-bit integer (ticks) and 16-bit integer (offset)\n* `Decimal` as string\n* `Empty` as zero-sized map\n* `Enum` as string\n* `Guid` as byte array\n* `IntPtr` as 64-bit integer\n* `IReadOnlyDictionary\u003cT, K\u003e` as map from `T` to `K` (recur on this list)\n* `IReadOnlyList\u003cT\u003e` as array of `T` (recor on this list)\n* `Nullable\u003cT\u003e` as either null or `T` (recur on this list)\n* `TimeSpan` as 64-bit integer\n* `Tuple\u003c\u003e` and `ValueTuple\u003c\u003e` as array of values (recur on this list)\n* `Union\u003c\u003e` as two-element array: type identifier as string, value (recur on this list)\n* `Version` as string\n* Finally, if none of the above apply, `class` and `struct` as heterogeneous MsgPack map from name to value\n\n---\n\n# License\n\nCopyright 2015-2017 Drew Noakes\n\n\u003e Licensed under the Apache License, Version 2.0 (the \"License\");\n\u003e you may not use this file except in compliance with the License.\n\u003e You may obtain a copy of the License at\n\u003e\n\u003e     http://www.apache.org/licenses/LICENSE-2.0\n\u003e\n\u003e Unless required by applicable law or agreed to in writing, software\n\u003e distributed under the License is distributed on an \"AS IS\" BASIS,\n\u003e WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\u003e See the License for the specific language governing permissions and\n\u003e limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoint-platform%2Fdasher","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpoint-platform%2Fdasher","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpoint-platform%2Fdasher/lists"}