{"id":13629687,"url":"https://github.com/ZingBallyhoo/StackXML","last_synced_at":"2025-04-17T09:35:45.888Z","repository":{"id":75657826,"uuid":"316770387","full_name":"ZingBallyhoo/StackXML","owner":"ZingBallyhoo","description":"Stack based zero-allocation XML serializer and deserializer powered by C# 9 source generators","archived":false,"fork":false,"pushed_at":"2022-11-21T18:48:01.000Z","size":65,"stargazers_count":43,"open_issues_count":0,"forks_count":8,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-08-01T22:44:10.046Z","etag":null,"topics":["csharp","csharp-sourcegenerator","dotnet","roslyn","source-generation","source-generators","xml"],"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/ZingBallyhoo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-11-28T16:04:19.000Z","updated_at":"2024-07-07T16:04:36.000Z","dependencies_parsed_at":"2023-04-18T23:31:14.783Z","dependency_job_id":null,"html_url":"https://github.com/ZingBallyhoo/StackXML","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZingBallyhoo%2FStackXML","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZingBallyhoo%2FStackXML/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZingBallyhoo%2FStackXML/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZingBallyhoo%2FStackXML/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZingBallyhoo","download_url":"https://codeload.github.com/ZingBallyhoo/StackXML/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223751369,"owners_count":17196624,"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":["csharp","csharp-sourcegenerator","dotnet","roslyn","source-generation","source-generators","xml"],"created_at":"2024-08-01T22:01:16.626Z","updated_at":"2025-04-17T09:35:45.882Z","avatar_url":"https://github.com/ZingBallyhoo.png","language":"C#","readme":"# StackXML\nStack based zero*-allocation XML serializer and deserializer powered by C# 9 source generators.\n\n## Why\nPremature optimisation :)\n\n## Setup\n- Add the following to your project to reference the serializer and enable the source generator\n```xml\n\u003cItemGroup\u003e\n    \u003cProjectReference Include=\"..\\StackXML\\StackXML.csproj\" /\u003e\n    \u003cProjectReference Include=\"..\\StackXML.Generator\\StackXML.Generator.csproj\" OutputItemType=\"Analyzer\" ReferenceOutputAssembly=\"false\" /\u003e\n\u003c/ItemGroup\u003e\n``` \n- The common entrypoint for deserializing is `XmlReadBuffer.ReadStatic(ReadOnlySpan\u003cchar\u003e)`\n- The common entrypoint for serializing is `XmlWriteBuffer.SerializeStatic(IXmlSerializable)`\n  - This method returns a string, to avoid this allocation you will need create your own instance of XmlWriteBuffer and ensure it is disposed safely like `SerializeStatic` does. The `ToSpan` method returns the char span containing the serialized text\n\n## Features\n- Fully structured XML serialization and deserialization with 0 allocations, apart from the output data structure when deserializing. Serialization uses a pooled buffer from `ArrayPool\u003cchar\u003e.Shared` that is released when the serializer is disposed.\n  - `XmlReadBuffer` handles deserialization\n  - `XmlWriteBuffer` handles serialization\n  - `XmlCls` maps a type to an element\n    - Used for the serializer to know what the element name should be\n    - Used by the deserializer to map to IXmlSerializable bodies with no explicit name\n  - `XmlField` maps to attributes\n  - `XmlBody` maps to child elements\n  - `IXmlSerializable` (not actually an interface, see quirks) represents a type that can be read from or written to XML\n    - Can be manually added as a base, or the source generator will add it automatically to any type that has XML attributes\n- Parsing delimited attributes into typed lists\n  - `\u003ctest list='1,2,3,4,6,7,8,9'\u003e`\n  - `[XmlField(\"list\")] [XmlSplitStr(',')] public List\u003cint\u003e m_list;`\n  - Using StrReader and StrWriter, see below\n- StrReader and StrWriter classes, for reading and writing (comma usually) delimited strings with 0 allocations.\n  - Can be used in a fully structured way by adding `StrField` attributes to fields on a `ref partial struct` (not compatible with XmlSplitStr, maybe future consideration)\n- Agnostic logging through [LibLog](https://github.com/damianh/LibLog)\n\n## Quirks\n- Invalid data between elements is ignored\n  - `\u003ctest\u003eanything here is completely missed\u003ctestInner/\u003e\u003ctest/\u003e`\n- Spaces between attributes is not required by the deserializer\n  - e.g `\u003ctest one='aa'two='bb'\u003e` \n- XmlSerializer must be disposed otherwise the pooled buffer will be leaked.\n  - XmlSerializer.SerializeStatic gives of an example of how this should be done in a safe way\n- Data types can only be classes, not structs.\n  - All types must inherit from IXmlSerializable (either manually or added by the source generator) which is actually an abstract class and not an interface\n  - Using structs would be possible but I don't think its worth the box\n- ~~Types from another assembly can't be used as a field/body. Needs fixing~~\n- All elements in the data to parse must be defined in the type in one way or another, otherwise an exception will be thrown.\n  - The deserializer relies on complete parsing and has no way of skipping elements\n- Comments within a primitive type body will cause the parser to crash (future consideration...)\n  - `\u003cn\u003e\u003c!--uh oh--\u003ehi\u003cn\u003e`\n- Null strings are currently output exactly the same as empty strings... might need changing\n- The source generator emits a parameterless constructor on all XML types that initializes `List\u003cT\u003e` bodies to an empty list\n  - Trying to serialize a null list currently crashes the serializer....\n- When decoding XML text an extra allocation of the input string is required\n  - WebUtility.HtmlDecode does not provide an overload taking a span, but the method taking a string turns it into a span anyway.. hmm\n  - The decode is avoided where possible\n- Would be nice to be able to use [ValueStringBuilder](https://github.com/dotnet/runtime/blob/master/src/libraries/Common/src/System/Text/ValueStringBuilder.cs). See https://github.com/dotnet/runtime/issues/25587\n\n## Performance\nVery simple benchmark, loading a single element and getting the string value of its attribute `attribute`\n``` ini\n\nBenchmarkDotNet=v0.13.0, OS=Windows 10.0.19045\nIntel Core i5-6600K CPU 3.50GHz (Skylake), 1 CPU, 4 logical and 4 physical cores\n.NET SDK=9.0.200\n  [Host]     : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT\n  DefaultJob : .NET 9.0.2 (9.0.225.6610), X64 RyuJIT\n```\n|        Method |        Mean |     Error |    StdDev |  Ratio | RatioSD |  Gen 0 | Gen 1 | Gen 2 | Allocated |\n|-------------- |------------:|----------:|----------:|-------:|--------:|-------:|------:|------:|----------:|\n|    ReadBuffer |    60.16 ns |  0.791 ns |  0.740 ns |   1.00 |    0.00 | 0.0178 |     - |     - |      56 B |\n|    XmlReader_ |   823.91 ns |  6.864 ns |  6.421 ns |  13.70 |    0.23 | 3.2892 |     - |     - |  10,336 B |\n|    XDocument_ | 1,047.87 ns | 17.032 ns | 15.931 ns |  17.42 |    0.27 | 3.4218 |     - |     - |  10,760 B |\n|   XmlDocument | 1,435.48 ns | 15.425 ns | 14.428 ns |  23.87 |    0.43 | 3.9063 |     - |     - |  12,248 B |\n| XmlSerializer | 6,398.11 ns | 88.037 ns | 82.350 ns | 106.37 |    2.14 | 4.5471 |     - |     - |  14,305 B |\n\n## Example data classes\n### Simple Attribute\n```xml\n\u003ctest attribute='value'/\u003e\n```\n```csharp\n[XmlCls(\"test\"))]\npublic partial class Test\n{\n    [XmlField(\"attribute\")]\n    public string m_attribute;\n}\n```\n### Text body\n```xml\n\u003ctest2\u003e\n    \u003cname\u003e\u003c![CDATA[Hello world]]\u003e\u003c/name\u003e\n\u003c/test2\u003e\n```\nCData can be configured by setting `cdataMode` for serializing and deserializing\n```xml\n\u003ctest2\u003e\n    \u003cname\u003eHello world\u003c/name\u003e\n\u003c/test2\u003e\n```\n```csharp\n[XmlCls(\"test2\"))]\npublic partial class Test2\n{\n    [XmlBody(\"name\")]\n    public string m_name;\n}\n```\n### Lists\n```xml\n\u003ccontainer\u003e\n    \u003clistItem name=\"hey\" age='25'/\u003e\n    \u003clistItem name=\"how\" age='2'/\u003e\n    \u003clistItem name=\"are\" age='4'/\u003e\n    \u003clistItem name=\"you\" age='53'/\u003e\n\u003c/container\u003e\n```\n```csharp\n[XmlCls(\"listItem\"))]\npublic partial class ListItem\n{\n    [XmlField(\"name\")]\n    public string m_name;\n    \n    [XmlField(\"age\")]\n    public int m_age; // could also be byte, uint etc\n}\n\n[XmlCls(\"container\")]\npublic partial class ListContainer\n{\n    [XmlBody()]\n    public List\u003cListItem\u003e m_items; // no explicit name, is taken from XmlCls\n}\n```\n### Delimited attributes\n```xml\n\u003cmusicTrack id='5' artists='5,6,1,24,535'\u003e\n    \u003cn\u003e\u003c![CDATA[Awesome music]]\u003e\u003c/n\u003e\n    \u003ctags\u003ecool\u003c/tags\u003e\n    \u003ctags\u003eawesome\u003c/tags\u003e\n    \u003ctags\u003efresh\u003c/tags\u003e\n\u003c/musicTrack\u003e\n```\n```csharp\n[XmlCls(\"musicTrack\"))]\npublic partial class MusicTrack\n{\n    [XmlField(\"id\")]\n    public int m_id;\n    \n    [XmlBody(\"n\")]\n    public string m_name;\n    \n    [XmlField(\"artists\"), XmlSplitStr(',')]\n    public List\u003cint\u003e m_artists;\n    \n    [XmlBody(\"tags\")]\n    public List\u003cstring\u003e m_tags;\n}\n```","funding_links":[],"categories":["Source Generators","Do not want to test 112 ( old ISourceGenerator )"],"sub_categories":["Serialization","1. [ThisAssembly](https://ignatandrei.github.io/RSCG_Examples/v2/docs/ThisAssembly) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FZingBallyhoo%2FStackXML","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FZingBallyhoo%2FStackXML","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FZingBallyhoo%2FStackXML/lists"}