{"id":32372111,"url":"https://github.com/devlooped/structid","last_synced_at":"2026-04-14T02:04:07.230Z","repository":{"id":263688305,"uuid":"891191398","full_name":"devlooped/StructId","owner":"devlooped","description":"Automatic implemention of stronly typed IDs via record structs with compile-time feature auto-detection","archived":false,"fork":false,"pushed_at":"2025-09-30T00:31:32.000Z","size":270,"stargazers_count":37,"open_issues_count":11,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-09-30T02:34:38.600Z","etag":null,"topics":["csharp-sourcegenerator","roslyn","roslyn-analyzer","roslyn-code-analysis","roslyn-codefix","roslyn-generator"],"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/devlooped.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"license.txt","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"devlooped"}},"created_at":"2024-11-19T22:20:57.000Z","updated_at":"2025-06-05T01:46:13.000Z","dependencies_parsed_at":"2024-12-21T04:26:31.812Z","dependency_job_id":"3e72619d-2abd-448d-8d10-e49563db5e09","html_url":"https://github.com/devlooped/StructId","commit_stats":null,"previous_names":["devlooped/structid"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/devlooped/StructId","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FStructId","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FStructId/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FStructId/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FStructId/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devlooped","download_url":"https://codeload.github.com/devlooped/StructId/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devlooped%2FStructId/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280872028,"owners_count":26405606,"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","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"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":["csharp-sourcegenerator","roslyn","roslyn-analyzer","roslyn-code-analysis","roslyn-codefix","roslyn-generator"],"created_at":"2025-10-24T21:50:15.831Z","updated_at":"2026-04-14T02:04:07.222Z","avatar_url":"https://github.com/devlooped.png","language":"C#","readme":"![Icon](assets/img/icon-32.png) StructId\n============\n\n[![Version](https://img.shields.io/nuget/vpre/StructId.svg?color=royalblue)](https://www.nuget.org/packages/StructId)\n[![Downloads](https://img.shields.io/nuget/dt/StructId.svg?color=darkmagenta)](https://www.nuget.org/packages/StructId)\n[![EULA](https://img.shields.io/badge/EULA-OSMF-blue?labelColor=black\u0026color=C9FF30)](https://github.com/devlooped/oss/blob/main/osmfeula.txt)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/devlooped/oss/blob/main/license.txt)\n[![Build](https://github.com/devlooped/StructId/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/devlooped/StructId/actions)\n\n\u003c!-- #content --\u003e\nAn opinionated strongly-typed ID library that uses `readonly record struct` in C# for \nmaximum performance, minimal memory allocation typed identifiers.\n\n```csharp\npublic readonly partial record struct UserId : IStructId\u003cGuid\u003e;\n// String-based ID\npublic readonly partial record struct ProductId : IStructId;\n```\n\nUnlike other such libraries for .NET, StructId introduces several unique features:\n\n1. **Zero** run-time dependencies: everything is source-generated in your project.\n1. **Zero** configuration: additional features are automatically added as you reference \n   dependencies that require them. For example: if your project references EF Core, \n   Dapper, or Newtonsoft.Json, the corresponding serialization and deserialization \n   code will be emitted without any additional configuration for the generation itself.\n1. Leverages newest language and runtime features for cleaner and more efficient code, \n   such as:\n   * `IParsable\u003cT\u003e`/`ISpanParsable\u003cT\u003e` for parsing from strings.\n   * Static interface members, for consistent `TSelf.New(TValue value)` factory \n      method and proper type constraint (via a provided `INewable\u003cTSelf, TValue\u003e` interface).\n   * File-scoped compiled C# templates for unparalleled authoring and extensibility experience.\n\n## Usage\n\nAfter installing the [StructId package](https://nuget.org/packages/StructId), the project \n(with a direct reference to the `StructId` package) will contain the main interfaces \n`IStruct` (for string-typed IDs) and `IStructId\u003cTValue\u003e`. \n\n\u003e NOTE: the package only needs to be installed in the top-level project in your solution, \n\u003e since analyzers/generators will [automatically propagate to referencing projects]((https://github.com/dotnet/sdk/issues/1212)).\n\nThe package is a [development dependency](https://github.com/NuGet/Home/wiki/DevelopmentDependency-support-for-PackageReference), \nmeaning it will not add any run-time dependencies to your project (or package if you \npublish one that uses struct ids).\n\nYou can simply declare a new ID type by implementing `IStructId\u003cTValue\u003e`:\n\n```csharp\npublic readonly partial record struct UserId : IStructId\u003cGuid\u003e;\n```\n\nIf the declaration is missing `partial`, `readonly` or `record struct`, a codefix will\nbe offered to correct it.\n\n![codefix](https://raw.githubusercontent.com/devlooped/StructId/main/assets/img/record-codefix.png)\n\nThe relevant constructor and `Value` property will be generated for you, as well as \nas a few other common interfaces, such as `IComparable\u003cT\u003e`, `IParsable\u003cTSelf\u003e`, etc.\n\nIf you want to customize the primary constructor (i.e. to add custom attributes), \nyou can provide it yourself too:\n\n```csharp\npublic readonly partial record struct ProductId(int Value) : IStructId\u003cint\u003e;\n```\n\nIt must contain a single parameter named `Value` (and codefixes will offer to rename or \nremove it if you don't need it anymore).\n\n## EF Core\n\nIf you are using EF Core, the package will automatically generate the necessary value converters, \nas well as an `UseStructId` extension method for `DbContextOptionsBuilder` to set them up:\n\n```csharp\nvar options = new DbContextOptionsBuilder\u003cContext\u003e()\n    .UseSqlite(\"Data Source=ef.db\")\n    .UseStructId()\n    .Options;\n\nusing var context = new Context(options);\n// access your entities using struct ids\n```\n\nAlternatively, you can also invoke that method in the `OnConfiguring` method of your context:\n```csharp\nprotected override void OnConfiguring(DbContextOptionsBuilder builder) =\u003e builder.UseStructId();\n```\n\nIn addition to the natively supported primitive types, any other types that implement `IParsable\u003cT\u003e` \nand `IFormattable` get automatic support by persisting as strings. This means that you can, \nfor example, use [Ulid](https://github.com/Cysharp/Ulid) out of the box without any further \nconfiguration or customization (since it implements both interfaces).\n\nAdditionally, the `UseStructId` will pick up any custom [ValueConverter\u003cTModel,TProvider\u003e](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.storage.valueconversion.valueconverter-2?view=efcore-9.0) \nyou may add to your project and register them too for convenience.\n\n## Dapper\n\nIf you are using Dapper, the package will automatically generate required `SqlMapper.TypeHandler\u003cT\u003e` \nfor your ID types. The `UseStructId` extension method for `IDbConnection` can be used to register \nthem as needed:\n\n```csharp\nusing var connection = new SqliteConnection(\"Data Source=sqlite.db\")\n\nconnection.UseStructId();\nconnection.Open();\n```\n\nThe value types `Guid`, `int`, `long` and `string` have built-in support, as well as \nany other types that implement `IParsable\u003cT\u003e` and `IFormattable` (by persisting them \nas strings). This means that you can, for example, use [Ulid](https://github.com/Cysharp/Ulid) \nout of the box without any further configuration or customization (since it implements \nboth interfaces).\n\nAdditionally, the `UseStructId` will pick up any custom `Dapper.SqlMapper.TypeHandler\u003cT\u003e`\nyou may add to your project and register them too for convenience.\n\n## Ulid\n\nSince [Ulid](https://github.com/Cysharp/Ulid) implements `IParsable\u003cT\u003e` and `IFormattable`,\nit is supported out of the box without any further configuration or customization with both \nDapper and EF Core.\n\nIn addition to the necessary converter/handler registration, the package will also generate \na `New()` (parameterless) factory method for struct ids using `Ulid` as the value type, which \nin turn will use `Ulid.NewUlid()` to generate a new value. This mirrors the behavior \ngenerated for `Guid`-based struct ids.\n\n```csharp\npublic readonly partial record struct ProductId : IStructId\u003cUlid\u003e;\n\npublic record Product(ProductId Id, string Name);\n\n// Create a new product with a new Ulid-based id\nvar productId = ProductId.New(); // 👈 equivalent to: new ProductId(Ulid.NewUlid())\nvar product = new Product(productId, \"Product\");\n\n// Seed data\nconnection.Execute(\"INSERT INTO Products (Id, Name) VALUES (@Id, @Name)\", new Product(ProductId.New(), \"Product1\"));\nconnection.Execute(\"INSERT INTO Products (Id, Name) VALUES (@Id, @Name)\", product);\nconnection.Execute(\"INSERT INTO Products (Id, Name) VALUES (@Id, @Name)\", new Product(ProductId.New(), \"Product2\"));\n\n// showcase we can query by the underlying ulid\nvar saved1 = connection.QueryFirst\u003cProduct\u003e(\"SELECT * FROM Products WHERE Id = @Id\", new { Id = productId });\n\n// showcase we can query by the ulid-based struct id\nvar saved2 = connection.QueryFirst\u003cProduct\u003e(\"SELECT * FROM Products WHERE Id = @Id\", new { Id = new ProductId(productId) });\n```\n\n## Customization via Templates\n\nVirtually all the built-in interfaces and implementations are generated using the same compiled \ntemplates mechanism available to you. Templates are regular C# files in your project with a \nfew constraints. Here's an example from the built-in ones:\n\n```csharp\nusing System;\nusing StructId;\n\n[TStructId]\nfile partial record struct TSelf(IUtf8SpanFormattable Value) : IUtf8SpanFormattable\n{\n    /// \u003cinheritdoc/\u003e\n    public bool TryFormat(Span\u003cbyte\u003e utf8Destination, out int bytesWritten, ReadOnlySpan\u003cchar\u003e format, IFormatProvider? provider) \n        =\u003e ((IUtf8SpanFormattable)Value).TryFormat(utf8Destination, out bytesWritten, format, provider);\n}\n```\n\nThis type is considered a template because it's marked with the `[TStructId]` attribute. \nThis introduces some restrictions that are enfored by an analyzer:\n1. The type must be a `partial record struct` since it will complement a partial declaration \n   of that type by the user (i.e. `partial record struct PersonId : IStructId\u003cGuid\u003e;`)\n1. The type must be [file-scoped](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/file), \n   which automatically prevents polluting your assembly with types that aren't intended for \n   direct consumption outside the template file itself.\n1. The template can optionally declare the type of ID value it supports by introducing the \n   primary constructor with a single parameter named `Value` of that type.\n1. The record itself must be named `TSelf`.\n\nThe template itself can introduce arbitrary code that will be emitted for each matching \nstruct id (i.e. all struct ids whose value type implements `IUtf8SpanFormattable` in this case). \nIn this example, the template simply offers a pass-through implementation of the `IUtf8SpanFormattable` \nvalue.\n\nAs another example, imagine you have some standardized way of treating IDs in your application, \nby providing an interface for them, which applies to all `Guid`-based IDs:\n\n```csharp\npublic interface IId\n{\n    Guid Id { get; }\n}\n```\n\nYou can now create a template that will automatically provide this interface for all struct \nids that use `Guid` as their value type as follows:\n\n```csharp\n[TStructId]\nfile partial record struct TSelf(Guid Value) : IId\n{\n    public Guid Id =\u003e Value;\n}\n```\n\nAnother example of a built-in template that applies to a single type of `TValue` is \nthe following:\n\n```csharp\nusing StructId;\n\n[TStructId]\nfile partial record struct TSelf(string Value)\n{\n    public static implicit operator string(TSelf id) =\u003e id.Value;\n    public static explicit operator TSelf(string value) =\u003e new(value);\n}\n```\n\nThis template is a proper C# compilation unit, so you can use any C# feature that \nyour project supports, since its output will also be emitted via a source generator \nin the same project for matching struct ids.\n\nIn the case of a struct id defined as follows:\n\n```csharp\npublic partial record struct PersonId : IStructId\u003cGuid\u003e;\n```\n\nThe template will be applied automatically and result in a partial declaration \nlike:\n\n```csharp\npartial record struct PersonId : IId\n{\n    public Guid Id =\u003e Value;\n}\n```\n\nThings to note at template expansion time:\n1. The `[TStructId]` attribute is removed from the generated type automatically.\n2. The `TSelf` type is replaced with the actual name of the struct id.\n3. The primary constructor on the template is removed since it is already provided \n   by another generator.\n\nYou can also constrain the type of `TValue` the template applies to by using using \nthe special name `TValue` for the primary constructor parameter type, as in the following \nexample from the implicit conversion template:\n\n```csharp\nusing StructId;\n\n[TStructId]\nfile partial record struct TSelf(TValue Value)\n{\n    public static implicit operator TValue(TSelf id) =\u003e id.Value;\n    public static explicit operator TSelf(TValue value) =\u003e new(value);\n}\n\n// This will be removed when applying the template to each user-defined struct id.\nfile record struct TValue;\n```\n\nThe `TValue` is subsequently defined as a file-local type where you can \nspecify any interfaces it implements. If no constraints need to apply to \n`TValue`, you can just leave the declaration empty, meaning \"any value type\".\n\n\u003e NOTE: The type of declaration (struct, class, record, etc.) of `TValue` is not checked, \n\u003e since in many cases you'd end up having to create two versions of the same template, \n\u003e one for structs and another for strings, since they are not value types and have no \n\u003e common declaration type.\n\nHere's another example from the built-in templates that uses this technique to\napply to all struct ids whose `TValue` implements `IComparable\u003cTValue\u003e`:\n\n```csharp\nusing System;\nusing StructId;\n\n[TStructId]\nfile partial record struct TSelf(TValue Value) : IComparable\u003cTSelf\u003e\n{\n    /// \u003cinheritdoc/\u003e\n    public int CompareTo(TSelf other) =\u003e ((IComparable\u003cTValue\u003e)Value).CompareTo(other.Value);\n\n    /// \u003cinheritdoc/\u003e\n    public static bool operator \u003c(TSelf left, TSelf right) =\u003e left.Value.CompareTo(right.Value) \u003c 0;\n\n    /// \u003cinheritdoc/\u003e\n    public static bool operator \u003c=(TSelf left, TSelf right) =\u003e left.Value.CompareTo(right.Value) \u003c= 0;\n\n    /// \u003cinheritdoc/\u003e\n    public static bool operator \u003e(TSelf left, TSelf right) =\u003e left.Value.CompareTo(right.Value) \u003e 0;\n\n    /// \u003cinheritdoc/\u003e\n    public static bool operator \u003e=(TSelf left, TSelf right) =\u003e left.Value.CompareTo(right.Value) \u003e= 0;\n}\n\n// This will be removed when applying the template to each user-defined struct id.\nfile record struct TValue : IComparable\u003cTValue\u003e\n{\n    public int CompareTo(TValue other) =\u003e throw new NotImplementedException();\n}\n```\n\nThis automatically covers not only all built-in value types, but also any custom \ntypes that implement the interface, making the code generation much more flexible \nand powerful.\n\n\u003e NOTE: if you need to exclude just the string type from applying to the `TValue`, \n\u003e you can use the inline comment `/*!string*/` in the primary constructor parameter \n\u003e type, as in `TSelf(/*!string*/ TValue Value)`.\n\nIn addition to constraining on the `TValue` type, you can also constrain on the\nthe struct id/`TSelf` itself by declaring the inheritance requirements in a partial \nclass of `TSelf` in the template. For example, the following (built-in) template \nensures it's only applied to struct ids whose `TValue` is [Ulid](https://github.com/Cysharp/Ulid) \nand implement `INewable\u003cTSelf, Ulid\u003e`. This is useful in this case since the given \ninterface constraint allows us to use the `TSelf.New(Ulid)` static interface \nfactory method and have it recognized by the C# compiler as valid code as part of the \nimplementation of introduced parameterless `New()` factory method provided by the template:\n\n```csharp\n[TStructId]\nfile partial record struct TSelf(Ulid Value)\n{\n    public static TSelf New() =\u003e new(Ulid.NewUlid());\n}\n\n// This will be removed when applying the template to each user-defined struct id.\nfile partial record struct TSelf : INewable\u003cTSelf, Ulid\u003e\n{\n    public static TSelf New(Ulid value) =\u003e throw new NotImplementedException();\n}\n```\n\n\u003e NOTE: the built-in templates will always emit an implementation of \n\u003e `INewable\u003cTSelf, TValue\u003e` for all struct ids.\n \nHere you can see that the constraint that the value type must be `Ulid` is enforced by \nthe `TValue` constructor parameter type, while the interface constraint in the partial \ndeclaration enforces inheritance from `INewable\u003cTSelf, Ulid\u003e`. Since this part of \nthe partial declaration is removed, there is no need to provide an actual implementation \nfor the constrain interface(s), just the signature is enough. But the partial declaration\nproviding the interface constraint is necessary for the C# compiler to recognize the \nline with `public static TSelf New() =\u003e new(Ulid.NewUlid());` as valid.\n\n\u003c!-- #content --\u003e\n\u003c!-- #ci --\u003e\n\n# Dogfooding\n\n[![CI Version](https://img.shields.io/endpoint?url=https://shields.kzu.app/vpre/StructId/main\u0026label=nuget.ci\u0026color=brightgreen)](https://pkg.kzu.app/index.json)\n[![Build](https://github.com/devlooped/StructId/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/devlooped/StructId/actions)\n\nWe also produce CI packages from branches and pull requests so you can dogfood builds as quickly as they are produced. \n\nThe CI feed is `https://pkg.kzu.app/index.json`. \n\nThe versioning scheme for packages is:\n\n- PR builds: *42.42.42-pr*`[NUMBER]`\n- Branch builds: *42.42.42-*`[BRANCH]`.`[COMMITS]`\n\n\u003c!-- include https://github.com/devlooped/.github/raw/main/osmf.md --\u003e\n## Open Source Maintenance Fee\n\nTo ensure the long-term sustainability of this project, users of this package who generate \nrevenue must pay an [Open Source Maintenance Fee](https://opensourcemaintenancefee.org). \nWhile the source code is freely available under the terms of the [License](license.txt), \nthis package and other aspects of the project require [adherence to the Maintenance Fee](osmfeula.txt).\n\nTo pay the Maintenance Fee, [become a Sponsor](https://github.com/sponsors/devlooped) at the proper \nOSMF tier. A single fee covers all of [Devlooped packages](https://www.nuget.org/profiles/Devlooped).\n\n\u003c!-- https://github.com/devlooped/.github/raw/main/osmf.md --\u003e\n\n\u003c!-- include https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n# Sponsors \n\n\u003c!-- sponsors.md --\u003e\n[![Clarius Org](https://avatars.githubusercontent.com/u/71888636?v=4\u0026s=39 \"Clarius Org\")](https://github.com/clarius)\n[![MFB Technologies, Inc.](https://avatars.githubusercontent.com/u/87181630?v=4\u0026s=39 \"MFB Technologies, Inc.\")](https://github.com/MFB-Technologies-Inc)\n[![Khamza Davletov](https://avatars.githubusercontent.com/u/13615108?u=11b0038e255cdf9d1940fbb9ae9d1d57115697ab\u0026v=4\u0026s=39 \"Khamza Davletov\")](https://github.com/khamza85)\n[![SandRock](https://avatars.githubusercontent.com/u/321868?u=99e50a714276c43ae820632f1da88cb71632ec97\u0026v=4\u0026s=39 \"SandRock\")](https://github.com/sandrock)\n[![DRIVE.NET, Inc.](https://avatars.githubusercontent.com/u/15047123?v=4\u0026s=39 \"DRIVE.NET, Inc.\")](https://github.com/drivenet)\n[![Keith Pickford](https://avatars.githubusercontent.com/u/16598898?u=64416b80caf7092a885f60bb31612270bffc9598\u0026v=4\u0026s=39 \"Keith Pickford\")](https://github.com/Keflon)\n[![Thomas Bolon](https://avatars.githubusercontent.com/u/127185?u=7f50babfc888675e37feb80851a4e9708f573386\u0026v=4\u0026s=39 \"Thomas Bolon\")](https://github.com/tbolon)\n[![Kori Francis](https://avatars.githubusercontent.com/u/67574?u=3991fb983e1c399edf39aebc00a9f9cd425703bd\u0026v=4\u0026s=39 \"Kori Francis\")](https://github.com/kfrancis)\n[![Reuben Swartz](https://avatars.githubusercontent.com/u/724704?u=2076fe336f9f6ad678009f1595cbea434b0c5a41\u0026v=4\u0026s=39 \"Reuben Swartz\")](https://github.com/rbnswartz)\n[![Jacob Foshee](https://avatars.githubusercontent.com/u/480334?v=4\u0026s=39 \"Jacob Foshee\")](https://github.com/jfoshee)\n[![](https://avatars.githubusercontent.com/u/33566379?u=bf62e2b46435a267fa246a64537870fd2449410f\u0026v=4\u0026s=39 \"\")](https://github.com/Mrxx99)\n[![Eric Johnson](https://avatars.githubusercontent.com/u/26369281?u=41b560c2bc493149b32d384b960e0948c78767ab\u0026v=4\u0026s=39 \"Eric Johnson\")](https://github.com/eajhnsn1)\n[![Jonathan ](https://avatars.githubusercontent.com/u/5510103?u=98dcfbef3f32de629d30f1f418a095bf09e14891\u0026v=4\u0026s=39 \"Jonathan \")](https://github.com/Jonathan-Hickey)\n[![Ken Bonny](https://avatars.githubusercontent.com/u/6417376?u=569af445b6f387917029ffb5129e9cf9f6f68421\u0026v=4\u0026s=39 \"Ken Bonny\")](https://github.com/KenBonny)\n[![Simon Cropp](https://avatars.githubusercontent.com/u/122666?v=4\u0026s=39 \"Simon Cropp\")](https://github.com/SimonCropp)\n[![agileworks-eu](https://avatars.githubusercontent.com/u/5989304?v=4\u0026s=39 \"agileworks-eu\")](https://github.com/agileworks-eu)\n[![Zheyu Shen](https://avatars.githubusercontent.com/u/4067473?v=4\u0026s=39 \"Zheyu Shen\")](https://github.com/arsdragonfly)\n[![Vezel](https://avatars.githubusercontent.com/u/87844133?v=4\u0026s=39 \"Vezel\")](https://github.com/vezel-dev)\n[![ChilliCream](https://avatars.githubusercontent.com/u/16239022?v=4\u0026s=39 \"ChilliCream\")](https://github.com/ChilliCream)\n[![4OTC](https://avatars.githubusercontent.com/u/68428092?v=4\u0026s=39 \"4OTC\")](https://github.com/4OTC)\n[![domischell](https://avatars.githubusercontent.com/u/66068846?u=0a5c5e2e7d90f15ea657bc660f175605935c5bea\u0026v=4\u0026s=39 \"domischell\")](https://github.com/DominicSchell)\n[![Adrian Alonso](https://avatars.githubusercontent.com/u/2027083?u=129cf516d99f5cb2fd0f4a0787a069f3446b7522\u0026v=4\u0026s=39 \"Adrian Alonso\")](https://github.com/adalon)\n[![torutek](https://avatars.githubusercontent.com/u/33917059?v=4\u0026s=39 \"torutek\")](https://github.com/torutek)\n[![mccaffers](https://avatars.githubusercontent.com/u/16667079?u=110034edf51097a5ee82cb6a94ae5483568e3469\u0026v=4\u0026s=39 \"mccaffers\")](https://github.com/mccaffers)\n[![Seika Logiciel](https://avatars.githubusercontent.com/u/2564602?v=4\u0026s=39 \"Seika Logiciel\")](https://github.com/SeikaLogiciel)\n[![Andrew Grant](https://avatars.githubusercontent.com/devlooped-user?s=39 \"Andrew Grant\")](https://github.com/wizardness)\n[![Lars](https://avatars.githubusercontent.com/u/1727124?v=4\u0026s=39 \"Lars\")](https://github.com/latonz)\n[![prime167](https://avatars.githubusercontent.com/u/3722845?v=4\u0026s=39 \"prime167\")](https://github.com/prime167)\n\n\n\u003c!-- sponsors.md --\u003e\n[![Sponsor this project](https://avatars.githubusercontent.com/devlooped-sponsor?s=118 \"Sponsor this project\")](https://github.com/sponsors/devlooped)\n\n[Learn more about GitHub Sponsors](https://github.com/sponsors)\n\n\u003c!-- https://github.com/devlooped/sponsors/raw/main/footer.md --\u003e\n","funding_links":["https://github.com/sponsors/devlooped","https://github.com/sponsors"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fstructid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevlooped%2Fstructid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevlooped%2Fstructid/lists"}