{"id":15160317,"url":"https://github.com/neuecc/zeroformatter","last_synced_at":"2025-09-30T11:31:58.494Z","repository":{"id":11816836,"uuid":"67928192","full_name":"neuecc/ZeroFormatter","owner":"neuecc","description":"Infinitely Fast Deserializer for .NET, .NET Core and Unity.","archived":true,"fork":false,"pushed_at":"2022-04-12T00:09:19.000Z","size":107975,"stargazers_count":2398,"open_issues_count":73,"forks_count":248,"subscribers_count":148,"default_branch":"master","last_synced_at":"2025-01-12T10:47:37.383Z","etag":null,"topics":["binaryformat","c-sharp","serializer","unity"],"latest_commit_sha":null,"homepage":"","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/neuecc.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}},"created_at":"2016-09-11T12:18:17.000Z","updated_at":"2024-12-31T19:27:11.000Z","dependencies_parsed_at":"2022-08-07T06:16:38.466Z","dependency_job_id":null,"html_url":"https://github.com/neuecc/ZeroFormatter","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuecc%2FZeroFormatter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuecc%2FZeroFormatter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuecc%2FZeroFormatter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neuecc%2FZeroFormatter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neuecc","download_url":"https://codeload.github.com/neuecc/ZeroFormatter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234732482,"owners_count":18878416,"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":["binaryformat","c-sharp","serializer","unity"],"created_at":"2024-09-26T22:44:00.165Z","updated_at":"2025-09-30T11:31:57.278Z","avatar_url":"https://github.com/neuecc.png","language":"C#","readme":"ZeroFormatter\n===\nFastest C# Serializer and Infinitely Fast Deserializer for .NET, .NET Core and Unity.\n\n[![Gitter](https://badges.gitter.im/neuecc/ZeroFormatter.svg)](https://gitter.im/neuecc/ZeroFormatter?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n\n![image](https://cloud.githubusercontent.com/assets/46207/20072942/ba760e70-a56d-11e6-918f-edf84f0187da.png)\n\nNote: this is **unfair** comparison, please see the [performance](https://github.com/neuecc/ZeroFormatter#performance) section for the details.\n\nWhy use ZeroFormatter?\n---\n* **Fastest** C# serializer, the code is extremely tuned by both implementation and binary layout(see: [performance](https://github.com/neuecc/ZeroFormatter#performance))\n* Deserialize/re-serialize is Infinitely fast because formatter can access to serialized data without parsing/packing(see: [architecture](https://github.com/neuecc/ZeroFormatter#architecture))\n* Strongly Typed and C# Code as schema, no needs to other IDL like `.proto`, `.fbs`...\n* Smart API, only to use `Serialize\u003cT\u003e` and `Deserialize\u003cT\u003e`\n* Full set of general purpose, multifunctional serializer, supports Union(Polymorphism) and native support of Dictionary, MultiDictionary(ILookup)\n* First-class support to Unity(IL2CPP), it's faster than native JsonUtility\n\nZeroFormatter is similar as [FlatBuffers](http://google.github.io/flatbuffers/) but ZeroFormatter has clean API(FlatBuffers API is too ugly, [see: sample](https://github.com/google/flatbuffers/blob/master/samples/SampleBinary.cs); we can not use regularly) and C# specialized. If you need to performance such as Game, Distributed Computing, Microservices, etc..., ZeroFormatter will help you.\n\nInstall\n---\nfor .NET, .NET Core\n\n* PM\u003e Install-Package [ZeroFormatter](https://www.nuget.org/packages/ZeroFormatter)\n\nfor Unity(Interfaces can reference both .NET 3.5 and Unity for share types), Unity binary exists on [ZeroFormatter/Releases](https://github.com/neuecc/ZeroFormatter/releases) as well. More details, please see the [Unity-Supports](https://github.com/neuecc/ZeroFormatter#unity-supports) section.\n\n* PM\u003e Install-Package [ZeroFormatter.Interfaces](https://www.nuget.org/packages/ZeroFormatter.Interfaces/)\n* PM\u003e Install-Package [ZeroFormatter.Unity](https://www.nuget.org/packages/ZeroFormatter.Unity)\n\nVisual Studio Analyzer\n\n* PM\u003e Install-Package [ZeroFormatter.Analyzer](https://www.nuget.org/packages/ZeroFormatter.Analyzer)\n\nQuick Start\n---\nDefine class and mark as `[ZeroFormattable]` and public properties mark `[Index]` and declare `virtual`, call `ZeroFormatterSerializer.Serialize\u003cT\u003e/Deserialize\u003cT\u003e`. \n\n```csharp\n// mark ZeroFormattableAttribute\n[ZeroFormattable]\npublic class MyClass\n{\n    // Index is key of serialization\n    [Index(0)]\n    public virtual int Age { get; set; }\n\n    [Index(1)]\n    public virtual string FirstName { get; set; }\n\n    [Index(2)]\n    public virtual string LastName { get; set; }\n\n    // When mark IgnoreFormatAttribute, out of the serialization target\n    [IgnoreFormat]\n    public string FullName { get { return FirstName + LastName; } }\n\n    [Index(3)]\n    public virtual IList\u003cint\u003e List { get; set; }\n}\n\nclass Program\n{\n    static void Main(string[] args)\n    {\n        var mc = new MyClass\n        {\n            Age = 99,\n            FirstName = \"hoge\",\n            LastName = \"huga\",\n            List = new List\u003cint\u003e { 1, 10, 100 }\n        };\n\n        var bytes = ZeroFormatterSerializer.Serialize(mc);\n        var mc2 = ZeroFormatterSerializer.Deserialize\u003cMyClass\u003e(bytes);\n\n        // ZeroFormatter.DynamicObjectSegments.MyClass\n        Console.WriteLine(mc2.GetType().FullName);\n    }\n}\n```\n\nSerializable target must mark `ZeroFormattableAttribute`, there public property must be `virtual` and requires `IndexAttribute`.\n\nAnalyzer\n---\nZeroFormatter.Analyzer helps object definition. Attributes, accessibility etc are detected and it becomes a compiler error.\n\n![zeroformatteranalyzer](https://cloud.githubusercontent.com/assets/46207/20078766/3ea54f14-a585-11e6-9873-b99cb5d9efe5.gif)\n\nIf you want to allow a specific type (for example, when registering a custom type), put `ZeroFormatterAnalyzer.json` at the project root and make the Build Action to `AdditionalFiles`.\n\n![image](https://cloud.githubusercontent.com/assets/46207/20149311/0e6f73d6-a6f4-11e6-91cb-44c771c267cb.png)\n\nThis is a sample of the contents of ZeroFormatterAnalyzer.json. \n\n```\n[ \"System.Uri\" ]\n```\n\nBuilt-in support types\n---\nAll primitives, All enums, `TimeSpan`,  `DateTime`, `DateTimeOffset`, `Guid`, `Tuple\u003c,...\u003e`, `KeyValuePair\u003c,\u003e`, `KeyTuple\u003c,...\u003e`, `Array`, `List\u003c\u003e`, `HashSet\u003c\u003e`, `Dictionary\u003c,\u003e`, `ReadOnlyCollection\u003c\u003e`, `ReadOnlyDictionary\u003c,\u003e`, `IEnumerable\u003c\u003e`, `ICollection\u003c\u003e`, `IList\u003c\u003e`, `ISet\u003c,\u003e`, `IReadOnlyCollection\u003c\u003e`, `IReadOnlyList\u003c\u003e`, `IReadOnlyDictionary\u003c,\u003e`, `ILookup\u003c,\u003e` and inherited `ICollection\u003c\u003e` with paramterless constructor. Support type can extend easily, see: [Extensibility](https://github.com/neuecc/ZeroFormatter#extensibility) section.\n\nDefine object rules\n---\nThere rules can detect ZeroFormatter.Analyzer.\n\n* Type must be marked with ZeroformattableAttribute.  \n* Public property must be marked with IndexAttribute or IgnoreFormatAttribute.\n* Public property's must needs both public/protected get and set accessor.\n* Public property's accessor must be virtual.\n* Class is only supported public property not field(If struct can define field).\n* IndexAttribute is not allowed duplicate number.\n* Class must needs a parameterless constructor.\n* Struct index must be started with 0 and be sequential.\n* Struct needs full parameter constructor of index property types.\n* Union type requires UnionKey property.\n* UnionKey does not support multiple keys.\n* All Union sub types must be inherited type.\n\nThe definition of struct is somewhat different from class. \n\n```csharp\n[ZeroFormattable]\npublic struct Vector2\n{\n    [Index(0)]\n    public float x;\n    [Index(1)]\n    public float y;\n\n    // arg0 = Index0, arg1 = Index1\n    public Vector2(float x, float y)\n    {\n        this.x = x;\n        this.y = y;\n    }\n}\n```\n\nStruct index must be started with 0 and be sequential and needs full parameter constructor of index property types.\n\neager/lazy-evaluation\n---\nZeroFormatter has two types of evaluation, \"eager-evaluation\" and \"lazy-evaluation\". If the type is lazy-evaluation, deserialization will be infinitely fast because it does not parse. If the user-defined class or type is `IList\u003c\u003e`, `IReadOnlyList\u003c\u003e`, `ILazyLookup\u003c\u003e`, `ILazyDicitonary\u003c\u003e`, `ILazyReadOnlyDictionary\u003c\u003e`, deserialization of that type will be lazily evaluated.\n\n```csharp\n// MyClass is lazy-evaluation, all properties are lazily\n[ZeroFormattable]\npublic class MyClass\n{\n    // int[] is eager-evaluation, when accessing Prop2, all values are deserialized\n    [Index(0)]\n    public virtual int[] Prop1 { get; set; }\n\n    // IList\u003cint\u003e is lazy-evaluation, when accessing Prop2 with indexer, only that index value is deserialized \n    [Index(1)]\n    public virtual IList\u003cint\u003e Prop2 { get; set; }\n}\n```\n\nIf you want to maximize the power of lazy-evaluation, define all collections with `IList\u003c\u003e`/`IReadOnlyList\u003c\u003e`.\n\n`ILazyLookup\u003c\u003e`, `ILazyDicitonary\u003c\u003e`, `ILazyReadOnlyDictionary\u003c\u003e` is special collection interface, it defined by ZeroFormatter. The values defined in these cases are deserialized very quickly because the internal structure is also serialized in its entirety and does not need to be rebuilt data structure. But there are some limitations instead. Key type must be primitive, enum or there KeyTuple only because the key must be deterministic.\n\n```csharp\n[ZeroFormattable]\npublic class MyClass\n{\n    [Index(0)]\n    public virtual ILazyDictionary\u003cint, int\u003e LazyDictionary { get; set; }\n\n    [Index(1)]\n    public virtual ILazyLookup\u003cint, int\u003e LazyLookup { get; set; }\n}\n\n// there properties can set from `AsLazy***` extension methods. \n\nvar mc = new MyClass();\n\nmc.LazyDictionary = Enumerable.Range(1, 10).ToDictionary(x =\u003e x).AsLazyDictionary();\nmc.LazyLookup = Enumerable.Range(1, 10).ToLookup(x =\u003e x).AsLazyLookup();\n```\n\nAs a precaution, the binary size will be larger because all internal structures are serialized. This is a tradeoff, please select the best case depending on the situation.\n\nArchitecture\n---\nWhen deserializing an object, it returns a byte[] wrapper object. When accessing the property, it reads the data from the offset information of the header(and cache when needed).\n\n![image](https://cloud.githubusercontent.com/assets/46207/20246782/e11518da-aa01-11e6-99c4-aa8e55f6b726.png)\n\nWhy must we define object in virtual? The reason is to converts access to properties into access to byte buffers.\n\nIf there is no change in data, reserialization is very fast because it writes the internal buffer data as it is. All serialized data can mutate and if the property type is fixed-length(primitive and some struct),  it is written directly to internal binary data so keep the reserialization speed. If property is variable-length(string, list, object, etc...) the type and property are marked dirty. And it serializes only the difference, it is faster than normal serialization.\n\n![](https://cloud.githubusercontent.com/assets/46207/20078613/9f9ddfda-a584-11e6-9d7c-b98f8a6ac70e.png)\n\n\u003e If property includes array/collection, ZeroFormatter can not track data was mutated so always marks dirty initially even if you have not mutated it. To avoid it, declare all collections with `IList\u003c\u003e` or `IReadOnlyList\u003c\u003e`.\n\nIf you want to define Immutable, you can use \"protected set\" and \"IReadOnlyList\u003c\u003e\".\n\n```csharp\n[ZeroFormattable]\npublic class ImmutableClass\n{\n    [Index(0)]\n    public virtual int ImmutableValue { get; protected set; }\n\n    // IReadOnlyDictionary, ILazyReadOnlyDictionary, etc, too.\n    [Index(1)]\n    public virtual IReadOnlyList\u003cint\u003e ImmutableList { get; protected set; }\n}\n```\n\nBinary size is slightly larger than Protobuf, MsgPack because of needs the header index area and all primitives are fixed-length(same size as FlatBuffers, smaller than JSON). It is a good idea to compress it to shrink the data size, gzip or LZ4(recommended, LZ4 is fast compression/decompression algorithm).\n\nVersioning\n---\nIf schema is growing, you can add Index.\n\n```csharp\n[ZeroFormattable]\npublic class Version1\n{\n    [Index(0)]\n    public virtual int Prop1 { get; set; }\n    [Index(1)]\n    public virtual int Prop2 { get; set; }\n    \n    // If deserialize from new data, ignored.\n}\n\n[ZeroFormattable]\npublic class Version2\n{\n    [Index(0)]\n    public virtual int Prop1 { get; set; }\n    [Index(1)]\n    public virtual int Prop2 { get; set; }\n    // You can add new property. If deserialize from old data, value is assigned default(T).\n    [Index(2)]\n    public virtual int NewType { get; set; }\n}\n```\n\nBut you can not delete index. If that index is unnecessary, please make it blank(such as [0, 1, 3]).\n\nOnly `class` definition is supported for versioning. Please note that `struct` is not supported.\n\nDateTime\n---\n`DateTime` is serialized to UniversalTime so lose the TimeKind. If you want to change local time, use ToLocalTime after converted. \n\n```csharp\n// in Tokyo, Japan Local Time(UTC+9)\nvar date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Local);\nConsole.WriteLine(date);\n\n// 1999/12/31 15:00:00(UTC)\nvar deserialized = ZeroFormatterSerializer.Deserialize\u003cDateTime\u003e(ZeroFormatterSerializer.Serialize(date));\n\n// 2000/1/1 00:00:00(in Tokyo, +9:00)\nvar toLocal = deserialized.ToLocalTime();\n```\n\nIf you want to save offset info, use DateTimeOffset instead of DateTime. \n\n```csharp\n// in Tokyo, Japan Local Time(UTC+9)\nvar date = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Local);\n\n// 2000/1/1, +9:00\nvar target = new DateTimeOffset(date);\n\n// 2000/1/1, +9:00\nvar deserialized = ZeroFormatterSerializer.Deserialize\u003cDateTimeOffset\u003e(ZeroFormatterSerializer.Serialize(target));\n```\n\nUnion\n---\nZeroFormatter supports Union(Polymorphic) type. It can define abstract class and `UnionAttributes`, `UnionKeyAttribute`.\n\n```csharp\npublic enum CharacterType\n{\n    Human, Monster\n}\n\n// UnionAttribute abstract/interface type becomes Union, arguments is union subtypes.\n// It needs single UnionKey to discriminate\n[Union(typeof(Human), typeof(Monster))]\npublic abstract class Character\n{\n    [UnionKey]\n    public abstract CharacterType Type { get; }\n}\n\n[ZeroFormattable]\npublic class Human : Character\n{\n    // UnionKey value must return constant value(Type is free, you can use int, string, enum, etc...)\n    public override CharacterType Type\n    {\n        get\n        {\n            return CharacterType.Human;\n        }\n    }\n\n    [Index(0)]\n    public virtual string Name { get; set; }\n\n    [Index(1)]\n    public virtual DateTime Birth { get; set; }\n\n    [Index(2)]\n    public virtual int Age { get; set; }\n\n    [Index(3)]\n    public virtual int Faith { get; set; }\n}\n\n[ZeroFormattable]\npublic class Monster : Character\n{\n    public override CharacterType Type\n    {\n        get\n        {\n            return CharacterType.Monster;\n        }\n    }\n\n    [Index(0)]\n    public virtual string Race { get; set; }\n\n    [Index(1)]\n    public virtual int Power { get; set; }\n\n    [Index(2)]\n    public virtual int Magic { get; set; }\n}\n```\n\nYou can use Union as following.\n\n```csharp\nvar demon = new Monster { Race = \"Demon\", Power = 9999, Magic = 1000 };\n\n// use UnionType(Character) formatter(be carefule, does not use concrete type)\nvar data = ZeroFormatterSerializer.Serialize\u003cCharacter\u003e(demon);\n\nvar union = ZeroFormatterSerializer.Deserialize\u003cCharacter\u003e(data);\n\n// you can discriminate by UnionKey.\nswitch (union.Type)\n{\n    case CharacterType.Monster:\n        var demon2 = (Monster)union;\n        demon2.Race...\n        demon2.Power..\n        demon2.Magic...\n        break;\n    case CharacterType.Human:\n        var human2 = (Human)union;\n        human2.Name...\n        human2.Birth...\n        human2.Age..\n        human2.Faith...\n        break;\n    default:\n        Assert.Fail(\"invalid\");\n        break;\n}\n```\n\nIf an unknown identification key arrives, an exception is thrown by default. However, it is also possible to return the default type - fallbackType.\n\n```csharp\n// If new type received, return new UnknownEvent()\n[Union(subTypes: new[] { typeof(MailEvent), typeof(NotifyEvent) }, fallbackType: typeof(UnknownEvent))]\npublic interface IEvent\n{\n    [UnionKey]\n    byte Key { get; }\n}\n\n[ZeroFormattable]\npublic class MailEvent : IEvent\n{\n    [IgnoreFormat]\n    public byte Key =\u003e 1;\n\n    [Index(0)]\n    public string Message { get; set; }\n}\n\n[ZeroFormattable]\npublic class NotifyEvent : IEvent\n{\n    [IgnoreFormat]\n    public byte Key =\u003e 1;\n\n    [Index(0)]\n    public bool IsCritical { get; set; }\n}\n\n[ZeroFormattable]\npublic class UnknownEvent : IEvent\n{\n    [IgnoreFormat]\n    public byte Key =\u003e 0;\n}\n```\n\nUnion can construct on execution time. You can mark `DynamicUnion` and make resolver on `AppendDynamicUnionResolver`.\n\n```csharp\n[DynamicUnion] // root of DynamicUnion\npublic class MessageBase\n{\n\n}\n\npublic class UnknownMessage : MessageBase { }\n\n[ZeroFormattable]\npublic class MessageA : MessageBase { }\n\n[ZeroFormattable]\npublic class MessageB : MessageBase\n{\n    [Index(0)]\n    public virtual IList\u003cIEvent\u003e Events { get; set; }\n}\n\nZeroFormatter.Formatters.Formatter.AppendDynamicUnionResolver((unionType, resolver) =\u003e\n{\n    //can be easily extended to reflection based scan if library consumer wants it\n    if (unionType == typeof(MessageBase))\n    {\n        resolver.RegisterUnionKeyType(typeof(byte));\n        resolver.RegisterSubType(key: (byte)1, subType: typeof(MessageA));\n        resolver.RegisterSubType(key: (byte)2, subType: typeof(MessageB));\n        resolver.RegisterFallbackType(typeof(UnknownMessage));\n    }\n});\n```\n\nUnity Supports\n---\nPut the `ZeroFormatter.dll` and `ZeroFormatter.Interfaces.dll`, modify Edit -\u003e Project Settings -\u003e Player -\u003e Optimization -\u003e Api Compatibillity Level to `.NET 2.0` or higher.\n\n![image](https://cloud.githubusercontent.com/assets/46207/20293228/d3a4add2-ab37-11e6-878b-24daad4dc2c1.png)\n\nZeroFormatter.Unity works on all platforms(PC, Android, iOS, etc...). But it can 'not' use dynamic serializer generation due to IL2CPP issue. But pre code generate helps it. Code Generator is located in `packages\\ZeroFormatter.Interfaces.*.*.*\\tools\\zfc.exe`. zfc is using [Roslyn](https://github.com/dotnet/roslyn) so analyze source code, pass the target `csproj`. \n\n```\nzfc arguments help:\n  -i, --input=VALUE             [required]Input path of analyze csproj\n  -o, --output=VALUE            [required]Output path(file) or directory base(in separated mode)\n  -s, --separate                [optional, default=false]Output files are separated\n  -u, --unuseunityattr          [optional, default=false]Unuse UnityEngine's RuntimeInitializeOnLoadMethodAttribute on ZeroFormatterInitializer\n  -t, --customtypes=VALUE       [optional, default=empty]comma separated allows custom types\n  -c, --conditionalsymbol=VALUE [optional, default=empty]conditional compiler symbol\n  -r, --resolvername=VALUE      [optional, default=DefaultResolver]Register CustomSerializer target\n  -d, --disallowinternaltype    [optional, default=false]Don't generate internal type\n  -e, --propertyenumonly        [optional, default=false]Generate only property enum type only\n  -m, --disallowinmetadata      [optional, default=false]Don't generate in metadata type\n  -g, --gencomparekeyonly       [optional, default=false]Don't generate in EnumEqualityComparer except dictionary key\n  -n, --namespace=VALUE         [optional, default=ZeroFormatter]Set namespace root name\n  -f, --forcedefaultresolver    [optional, default=false]Force use DefaultResolver\n```\n\n\u003e Note: Some options is important for reduce code generation size and startup speed on IL2CPP, especially `-f` is recommend if you use only DefaultResolver.\n\n```\n// Simple Case:\nzfc.exe -i \"..\\src\\Sandbox.Shared.csproj\" -o \"ZeroFormatterGenerated.cs\"\n\n// with t, c\nzfc.exe -i \"..\\src\\Sandbox.Shared.csproj\" -o \"..\\unity\\ZfcCompiled\\ZeroFormatterGenerated.cs\" -t \"System.Uri\" -c \"UNITY\"\n\n// -s\nzfc.exe -i \"..\\src\\Sandbox.Shared.csproj\" -s -o \"..\\unity\\ZfcCompiled\\\" \n```\n\n`zfc.exe` can setup on csproj's `PreBuildEvent`(useful to generate file path under self project) or `PostBuildEvent`(useful to generate file path is another project).\n\n\u003e Note: zfc.exe is currently only run on Windows. It is .NET Core's [Roslyn](https://github.com/dotnet/roslyn) workspace API limitation but I want to implements to all platforms...\n\nGenerated formatters must need to register on Startup. By default, zfc generate automatic register code on `RuntimeInitializeOnLoad` timing.\n\nFor Unity Unit Tests, the generated formatters must be registered in the `SetUp` method:\n\n```csharp\n    [SetUp]\n    public void RegisterZeroFormatter()\n    {\n        ZeroFormatterInitializer.Register();\n    }\n```\n\nZeroFormatter can not serialize Unity native types by default but you can make custom formatter by define pseudo type. For example create `Vector2` to ZeroFormatter target. \n\n```csharp\n#if INCLUDE_ONLY_CODE_GENERATION\n\nusing ZeroFormatter;\n\nnamespace UnityEngine\n{\n    [ZeroFormattable]\n    public struct Vector2\n    {\n        [Index(0)]\n        public float x;\n        [Index(1)]\n        public float y;\n\n        public Vector2(float x, float y)\n        {\n            this.x = x;\n            this.y = y;\n        }\n    }\n}\n\n#endif\n```\n\n`INCLUDE_ONLY_CODE_GENERATION` is special symbol of zfc, include generator target but does not include compile.\n\nIf you encounter `InvalidOperationException` such as \n\n```\nInvalidOperationException: Type is not supported, please register Vector3[]\n```\n\nIt means not generated/registered type. Especially collections are not automatically registered if they are not included in the property. You can register manually such as `Formatter.RegisterArray\u003cUnityEngine.Vector3\u003e()` or create hint type for zfc.\n\n```csharp\nusing ZeroFormatter;\n\nnamespace ZfcHint\n{\n    [ZeroFormattable]\n    public class TypeHint\n    {\n        // zfc analyzes UnityEngine.Vector3[] type and register it. \n        [Index(0)]\n        public UnityEngine.Vector3[] Hint1;\n    }\n}\n```\n\nPerformance\n---\nBenchmarks comparing to other serializers run on `Windows 10 Pro x64 Intel Core i7-6700 3.40GHz, 32GB RAM`. Benchmark code is [here](https://github.com/neuecc/ZeroFormatter/tree/master/sandbox/PerformanceComparison) and full-result is [here](https://gist.github.com/neuecc/f786e7161e0af9578d717942372bc1f4), latest compare with more serializers(Wire, NetSerializer, etc...) result is [here](https://github.com/neuecc/ZeroFormatter/issues/30).\n\n![](https://cloud.githubusercontent.com/assets/46207/20078590/81b890aa-a584-11e6-838b-5f2a4f1a11f8.png)\n\n![](https://cloud.githubusercontent.com/assets/46207/20077970/f3ce8044-a581-11e6-909d-e30b2a33e991.png)\n\nDeserialize speed is Infinitely fast(but of course, it is **unfair**, ZeroFormatter's deserialize is delayed when access target field). Serialize speed is fair-comparison. ZeroFormatter is fastest(compare to protobuf-net, 2~3x fast) for sure. ZeroFormatter has many reasons why fast.\n\n* Serializer uses only `ref byte[]` and `int offset`, don't use MemoryStream(call MemoryStream api is overhead)\n* Don't use variable-length number when encode number so there has encode cost(for example; protobuf uses ZigZag Encoding)\n* Acquire strict length of byte[] when knows final serialized length(for example; int, fixed-length list, string, etc...)\n* Avoid boxing all codes, all platforms(include Unity/IL2CPP)\n* Reduce native string encoder methods\n* Don't create intermediate utility instance(XxxWriter/Reader, XxxContext, etc...)\n* Heavyly tuned dynamic il code generation: [DynamicObjectFormatter.cs](https://github.com/neuecc/ZeroFormatter/blob/853a0d0c6b7de66b8447b426ab47b90336deca2c/src/ZeroFormatter/Formatters/DynamicFormatter.cs#L212-L980)\n* Getting cached generated formatter on static generic field(don't use dictinary-cache because dictionary lookup is overhead): [Formatter.cs](https://github.com/neuecc/ZeroFormatter/blob/853a0d0c6b7de66b8447b426ab47b90336deca2c/src/ZeroFormatter/Formatters/Formatter.cs)\n* Enum is serialized only underlying-value and uses fastest cast technique: [EnumFormatter.cs](https://github.com/neuecc/ZeroFormatter/blob/853a0d0c6b7de66b8447b426ab47b90336deca2c/src/ZeroFormatter/Formatters/EnumFormatter.cs)\n\nThe result is achieved from both sides of implementation and binary layout. ZeroFormatter's binary layout is tuned for serialize/deserialize speed(this is advantage than other serializer).\n\n**In Unity**\n\nResult run on iPhone 6s Plus and IL2CPP build.\n\n![](https://cloud.githubusercontent.com/assets/46207/20076797/281f7b78-a57d-11e6-8fbd-e83cc6b72025.png)\n\nZeroFormatter is faster than JsonUtility so yes, faster than native serializer! Why MsgPack-Cli is slow? MsgPack-Cli's Unity implemntation has a lot of hack of avoid AOT issues, it causes performance impact(especially struct, all codes pass boxing). ZeroFormatter codes is full tuned for Unity with/without IL2CPP.\n\n**Single Integer(1), Large String(represents HTML), Vector3 Struct(float, float, float), Vector3[100]**\n\n![image](https://cloud.githubusercontent.com/assets/46207/20247341/2393be4a-aa0d-11e6-8475-ec50bfefa687.png)\n![image](https://cloud.githubusercontent.com/assets/46207/20140306/c6b1b0fc-a6cd-11e6-9193-303179d23764.png)\n\nZeroFormatter is optimized for all types(small struct to large object!). I know why protobuf-net is slow on integer test, currently [protobuf-net's internal serialize method](https://github.com/mgravell/protobuf-net/blob/0d0bb407865600c7dad1b833a9a1f71ef48c7106/protobuf-net/Meta/TypeModel.cs#L210) has only `object value` so it causes boxing and critical for performance. Anyway, ZeroFormatter's simple struct and struct array(struct array is serialized FixedSizeList format internally, it is faster than class array)'s serialization/deserialization speed is very fast that effective storing value to KeyValueStore(like Redis) or network gaming(transport many transform position), etc.\n\nCompare with MessagePack for C#\n---\nAuthor also created [MessagePack for C#](https://github.com/neuecc/MessagePack-CSharp). It is fast, compact general purpose serializer. MessagePack for C# is a good choice if you are looking for a JSON-like, general purpose fast binary serializer. Built-in LZ4 support makes it suitable for network communication and storage in Redis. If you need infintely fast deserializer, ZeroFormatter is good choice.\n\nZeroFormatterSerializer API\n---\nWe usually use `Serialize\u003cT\u003e` and `Deserialize\u003cT\u003e`, but there are other APIs as well. `Convert\u003cT\u003e` is converted T to T but the return value is wrapped data. It is fast when reserialization so if you store the immutable data and serialize frequently, very effective. `IsFormattedObject\u003cT\u003e` can check the data is wrapped data or not.\n\n`Serialize\u003cT\u003e` has some overload, the architecture of ZeroFormatter is to write to byte [], read from byte [] so byte[] method is fast, first-class method. Stream method is helper API. ZeroFormatter has non-allocate API, as well. `int Serialize\u003cT\u003e(ref byte[] buffer, int offset, T obj)` expands the buffer but do not shrink. Return value int is size so you can pass the buffer from array pooling, ZeroFormatter does not allocate any extra memory.\n\nIf you want to use non-generic API, there are exists under `ZeroFormatterSerializer.NonGeneric`. It can pass Type on first-argument instead of `\u003cT\u003e`.\n\n\u003e NonGeneric API is not supported in Unity. NonGeneric API is a bit slower than the generic API. Because of the lookup of the serializer by type and the cost of boxing if the value is a value type are costly. We recommend using generic API if possible.\n\n`ZeroFormatterSerializer.MaximumLengthOfDeserialize` is max length of array(collection) length when deserializing. The default is 67108864, it includes `byte[]`(67MB). This limitation is for security issue(block of OutOfMemory). If you want to expand this limitation, set the new size.\n\nExtensibility\n---\nZeroFormatter can become custom binary layout framework. You can create own typed formatter. For example, add supports `Uri`.\n\n```csharp\n// \"\u003cTTypeResolver\u003e where TTypeResolver : ITypeResolver, new()\" is a common rule. in details, see: configuration section.\npublic class UriFormatter\u003cTTypeResolver\u003e : Formatter\u003cTTypeResolver, Uri\u003e\n    where TTypeResolver : ITypeResolver, new()\n{\n    public override int? GetLength()\n    {\n        // If size is variable, return null.\n        return null;\n    }\n\n    public override int Serialize(ref byte[] bytes, int offset, Uri value)\n    {\n        // Formatter\u003cT\u003e can get child serializer\n        return Formatter\u003cTTypeResolver, string\u003e.Default.Serialize(ref bytes, offset, value.ToString());\n    }\n\n    public override Uri Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)\n    {\n        var uriString = Formatter\u003cTTypeResolver, string\u003e.Default.Deserialize(ref bytes, offset, tracker, out byteSize);\n        return (uriString == null) ? null : new Uri(uriString);\n    }\n}\n```\n\nYou need to register formatter on application startup. \n\n```csharp\n// What is DefaultResolver? see: configuration section. \nZeroFormatter.Formatters.Formatter\u003cDefaultResolver, Uri\u003e.Register(new UriFormatter\u003cDefaultResolver\u003e());\n```\n\nOne more case, how to create generic formatter. For example, If implements `ImmutableList\u003cT\u003e`?\n\n```csharp\npublic class ImmutableListFormatter\u003cTTypeResolver, T\u003e : Formatter\u003cTTypeResolver, ImmutableList\u003cT\u003e\u003e\n    where TTypeResolver : ITypeResolver, new()\n{\n    public override int? GetLength()\n    {\n        return null;\n    }\n\n    public override int Serialize(ref byte[] bytes, int offset, ImmutableList\u003cT\u003e value)\n    {\n        // use sequence format.\n        if (value == null)\n        {\n            BinaryUtil.WriteInt32(ref bytes, offset, -1);\n            return 4;\n        }\n\n        var startOffset = offset;\n        offset += BinaryUtil.WriteInt32(ref bytes, offset, value.Count);\n\n        var formatter = Formatter\u003cTTypeResolver, T\u003e.Default;\n        foreach (var item in value)\n        {\n            offset += formatter.Serialize(ref bytes, offset, item);\n        }\n\n        return offset - startOffset;\n    }\n\n    public override ImmutableList\u003cT\u003e Deserialize(ref byte[] bytes, int offset, DirtyTracker tracker, out int byteSize)\n    {\n        byteSize = 4;\n        var length = BinaryUtil.ReadInt32(ref bytes, offset);\n        if (length == -1) return null;\n\n        var formatter = Formatter\u003cTTypeResolver, T\u003e.Default;\n        var builder = ImmutableList\u003cT\u003e.Empty.ToBuilder();\n        int size;\n        offset += 4;\n        for (int i = 0; i \u003c length; i++)\n        {\n            var val = formatter.Deserialize(ref bytes, offset, tracker, out size);\n            builder.Add(val);\n            offset += size;\n        }\n            \n        return builder.ToImmutable();\n    }\n}\n```\n\nAnd register generic resolver on startup.\n\n```csharp\n// append to default resolver.\nZeroFormatter.Formatters.Formatter.AppendFormatterResolver(t =\u003e\n{\n    if (t.IsGenericType \u0026\u0026 t.GetGenericTypeDefinition() == typeof(ImmutableList\u003c\u003e))\n    {\n        var formatter = typeof(ImmutableListFormatter\u003c\u003e).MakeGenericType(t.GetGenericArguments());\n        return Activator.CreateInstance(formatter);\n    }\n\n    return null; // fallback to the next resolver\n});\n```\n\nConfiguration\n---\nIf you want to create Formatter based on special rules generated by ResolveFormatter and RegisterDynamicUnion with the same AppDomain, you need to implement ITypeResolver.\n\n```csharp\npublic class CustomSerializationContext : ITypeResolver\n{\n    public bool IsUseBuiltinDynamicSerializer\n    {\n        get\n        {\n            return true;\n        }\n    }\n\n    public object ResolveFormatter(Type type)\n    {\n        // same as Formatter.AppendFormatResolver.\n        return null;\n    }\n\n    public void RegisterDynamicUnion(Type unionType, DynamicUnionResolver resolver)\n    {\n        // same as Formatter.AppendDynamicUnionResolver\n    }\n}\n```\n\n`ZeroFormatterSerializer.CustomSerializer\u003cCustomSerializationContext\u003e.Serialize/Deserialize` methods use those contexts. By default, `DefaultResolver` is used.\n\nWireFormat Specification\n---\nAll formats are represented in little endian. There are two lengths of binary, fixed-length and variable-length, which affect the `List Format`.\n\n**Primitive Format**\n\nPrimitive format is fixed-length(except string), eager-evaluation. C# `Enum` is serialized there underlying type. TimeSpan, DateTime is serialized UniversalTime and serialized format is same as Protocol Buffers's [timestamp.proto](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto).\n\n| Type | Layout | Note |\n| ---- | ------ | ---- |\n| Int16 | [short(2)] |\n| Int32 | [int(4)] |\n| Int64 | [long(8)] |\n| UInt16 | [ushort(2)] |\n| UInt32 | [uint(4)] |\n| UInt64 | [ulong(8)] |\n| Single | [float(4)] |\n| Double | [double(8)] |\n| Boolean | [bool(1)] |\n| Byte | [byte(1)] |\n| SByte | [sbyte(1)] |\n| Char | [ushort(2)] | UTF16-LE |\n| TimeSpan | [seconds:long(8)][nanos:int(4)] | seconds represents time from 00:00:00 |\n| DateTime | [seconds:long(8)][nanos:int(4)] | seconds represents UTC time since Unix epoch(0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z) |\n| DateTimeOffset | [seconds:long(8)][nanos:int(4)][offsetMinutes:short(2)] | DateTime with a time difference |\n| String | [utf8Bytes:(length)] | currently no used but reserved for future |\n| Int16? | [hasValue:bool(1)][short(2)] |\n| Int32? | [hasValue:bool(1)][int(4)] |\n| Int64? | [hasValue:bool(1)][long(8)] |\n| UInt16? | [hasValue:bool(1)][ushort(2)] |\n| UInt32? | [hasValue:bool(1)][uint(4)] |\n| UInt64? | [hasValue:bool(1)][ulong(8)] |\n| Single? | [hasValue:bool(1)][float(4)] |\n| Double? | [hasValue:bool(1)][double(8)] |\n| Boolean? | [hasValue:bool(1)][bool(1)] |\n| Byte? | [hasValue:bool(1)][byte(1)] |\n| SByte? | [hasValue:bool(1)][sbyte(1)] |\n| Char? | [hasValue:bool(1)][ushort(2)] | UTF16-LE |\n| TimeSpan? | [hasValue:bool(1)][seconds:long(8)][nanos:int(4)] | seconds represents time from 00:00:00 |\n| DateTime? | [hasValue:bool(1)][seconds:long(8)][nanos:int(4)] | seconds represents UTC time since Unix epoch(0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z) |\n| DateTimeOffset? | [hasValue:bool(1)][seconds:long(8)][nanos:int(4)][offsetMinutes:short(2)] | DateTime with a time difference |\n| String? | [length:int(4)][utf8Bytes:(length)] | representes `String`, if length = -1, indicates null. This is only variable-length primitive. |\n\n**Sequence Format**\n\nSequence is variable-length, eager-evaluation. Sequence represents a multiple object. If field is declared collection type(except `IList\u003cT\u003e, IReadOnlyList\u003cT\u003e`), used this format.\n\n| Type | Layout | Note |\n| ---- | ------ | ---- |\n| `Sequence\u003cT\u003e` | [length:int(4)][elements:T...] | if length = -1, indicates null |\n\n**List Format**\n\nList is variable-length, lazy-evaluation. If field is declared `IList\u003cT\u003e` or `IReadOnlyList\u003cT\u003e`, used this format.\n\n| Type | Layout | Note |\n| ---- | ------ | ---- |\n| FixedSizeList | [length:int(4)][elements:T...] | T is fixed-length format. if length = -1, indicates null |\n| VariableSizeList | [byteSize:int(4)][length:int(4)][elementOffset...:int(4 * length)][elements:T...] | T is variable-length format. if byteSize = -1, indicates null. indexOffset is relative position from list start offset |\n\n**Object Format**\n\nObject Format is a user-defined type.\n\nObject is variable-length, lazy-evaluation which has index header.\n\nStruct is eager-evaluation, if all field types are fixed-length which struct is marked fixed-length, else variable-length. This format is included `KeyTuple`, `Tuple`, `KeyValuePair(used by Dictionary)`, `IGrouping(used by ILookup)`.\n\n| Type | Layout | Note |\n| ---- | ------ | ---- |\n| Object | [byteSize:int(4)][lastIndex:int(4)][indexOffset...:int(4 * lastIndex)][Property1:T1, Property2:T2, ...] | used by class in default. if byteSize = -1, indicates null, indexOffset = 0, indicates blank. indexOffset is relative position from object start offset |\n| Struct | [Index1Item:T1, Index2Item:T2,...] | used by struct in default. This format can be fixed-length. versioning is not supported. |\n| Struct? | [hasValue:bool(1)][Index1Item:T1, Index2Item:T2,...] | used by struct in default. This format can be fixed-length. versioning is not supported. |\n\n**Union Format**\n\nUnion is variable-length, eager-evaluation, discriminated by key type to each value type.\n\n| Type | Layout | Note |\n| ---- | ------ | ---- |\n| Union | [byteSize:int(4)][unionKey:TKey][value:TValue] | if byteSize = -1, indicates null |\n\n**Extension Format**\n\nNot a standard format but builtin on C# implementation.\n\n| Type | Layout | Note |\n| ---- | ------ | ---- |\n| Decimal | [lo:int(4)][mid:int(4)][hi:int(4)][flags:int(4)] | fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead.  |\n| Decimal? | [hasValue:bool(1)][lo:int(4)][mid:int(4)][hi:int(4)][flags:int(4)] | fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead. |\n| Guid | [bytes:byteArray(16)] | fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead.  |\n| Guid? | [hasValue:bool(1)][bytes:byteArray(16)] | fixed-length, eager-evaluation. If you need to language-wide cross platform, use string instead. |\n| LazyDictionary | [byteSize:int(4)][length:int(4)][buckets:`FixedSizeList\u003cint\u003e`][entries:`VariableSizeList\u003cDictionaryEntry\u003e`] | represents `ILazyDictionary\u003cTKey, TValue\u003e`, if byteSize == -1, indicates null, variable-length, lazy-evaluation  |\n| DictionaryEntry | [hashCode:int(4)][next:int(4)][key:TKey][value:TValue] | substructure of LazyDictionary | \n| LazyMultiDictionary | [byteSize:int(4)][length:int(4)][groupings:`VariableSizeList\u003cVariableSizeList\u003cGroupingSemengt\u003e\u003e`] | represents `ILazyLookup\u003cTKey, TElement\u003e`, if byteSize == -1, indicates null, variable-length, lazy-evaluation | \n| GroupingSegment | [key:TKey] [hashCode:int(4)][elements:`VariableSizeList\u003cTElement\u003e`] | substructure of LazyMultiDictionary \n\n**EqualityComparer**\n\nZeroFormatter's EqualityComparer calculates stable hashCode for serialize LazyDictionary/LazyMultiDictionary. LazyDictionary and LazyMultiDictionary keys following there `GetHashCode` function. \n\n* [WireFormatEqualityComparers](https://github.com/neuecc/ZeroFormatter/blob/master/src/ZeroFormatter/Comparers/WireFormatEqualityComparers.cs)\n* [KeyTupleEqualityComparer](https://github.com/neuecc/ZeroFormatter/blob/master/src/ZeroFormatter/Comparers/KeyTupleEqualityComparer.cs)\n\nC# Schema\n---\nThe schema of ZeroFormatter is C# itself. You can define the schema in C# and analyze it with Roslyn to generate in another language or C#(such as zfc.exe).\n\n```csharp\nnamespace /* Namespace */\n{\n    // Fomrat Schemna\n    [ZeroFormattable]\n    public class /* FormatName */\n    {\n        [Index(/* Index Number */)]\n        public virtual /* FormatType */ Name { get; set; }\n    }\n\n    // UnionSchema\n    [Union(typeof(/* Union Subtypes */))]\n    public abstract class UnionSchema\n    {\n        [UnionKey]\n        public abstract /* UnionKey Type */ Key { get; }\n    }\n}\n```\n\nCross Platform\n---\nCurrently, No and I have no plans. Welcome to contribute port to other languages, I want to help your work!\n\nZeroFormatter spec has two stages + ex.\n\n* Stage1: All formats are eager-evaluation, does not support Extension Format.\n* Stage2: FixedSizeList, VariableSizeList and Object supports lazy-evaluation, does not support Extension Format.\n* StageEx: Supports C# Extension Format\n\nList of port libraries\n\n* Go, [shamaton/zeroformatter](https://github.com/shamaton/zeroformatter)\n* Ruby, [aki017/zero_formatter](https://github.com/aki017/zero_formatter)\n* Swift, [yaslab/ZeroFormatter.swift](https://github.com/yaslab/ZeroFormatter.swift)\n* Scala, [pocketberserker/scala-zero-formatter](https://github.com/pocketberserker/scala-zero-formatter/)\n* Rust, [pocketberserker/zero-formatter.rs](https://github.com/pocketberserker/zero-formatter.rs)\n* F#, [pocketberserker/ZeroFormatter.FSharpExtensions](https://github.com/pocketberserker/ZeroFormatter.FSharpExtensions)\n\nAuthor Info\n---\nYoshifumi Kawai(a.k.a. neuecc) is a software developer in Japan.  \nHe is the Director/CTO at Grani, Inc.  \nGrani is a top social game developer in Japan.  \nHe is awarding Microsoft MVP for Visual C# since 2011.  \nHe is known as the creator of [UniRx](http://github.com/neuecc/UniRx/)(Reactive Extensions for Unity)  \n\nBlog: https://medium.com/@neuecc (English)  \nBlog: http://neue.cc/ (Japanese)  \nTwitter: https://twitter.com/neuecc (Japanese)   \n\nLicense\n---\nThis library is under the MIT License.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuecc%2Fzeroformatter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneuecc%2Fzeroformatter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneuecc%2Fzeroformatter/lists"}