{"id":17081517,"url":"https://github.com/benfoster/minid","last_synced_at":"2025-04-12T21:09:20.781Z","repository":{"id":60464468,"uuid":"541408447","full_name":"benfoster/minid","owner":"benfoster","description":"Minid generates human-readable, URL-friendly, unique identifiers that are computed in-memory.","archived":false,"fork":false,"pushed_at":"2022-11-26T09:22:23.000Z","size":49,"stargazers_count":53,"open_issues_count":3,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-12T21:09:07.388Z","etag":null,"topics":["base32","unique-identifier"],"latest_commit_sha":null,"homepage":"https://dotnetfiddle.net/jim7YP","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/benfoster.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":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2022-09-26T04:34:48.000Z","updated_at":"2025-02-18T08:00:53.000Z","dependencies_parsed_at":"2022-09-30T00:30:17.312Z","dependency_job_id":null,"html_url":"https://github.com/benfoster/minid","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fminid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fminid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fminid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benfoster%2Fminid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benfoster","download_url":"https://codeload.github.com/benfoster/minid/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248631676,"owners_count":21136562,"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":["base32","unique-identifier"],"created_at":"2024-10-14T12:53:21.970Z","updated_at":"2025-04-12T21:09:20.726Z","avatar_url":"https://github.com/benfoster.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Minid\n\n[![NuGet](https://img.shields.io/nuget/v/minid.svg)](https://www.nuget.org/packages/minid) \n[![NuGet](https://img.shields.io/nuget/dt/minid.svg)](https://www.nuget.org/packages/minid)\n[![License](https://img.shields.io/:license-mit-blue.svg)](https://benfoster.mit-license.org/)\n\n![Build](https://github.com/benfoster/minid/workflows/Build/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/benfoster/minid/badge.svg?branch=main)](https://coveralls.io/github/benfoster/minid?branch=main)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=benfoster_minid\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=benfoster_minid)\n[![GuardRails badge](https://api.guardrails.io/v2/badges/146839?token=59af717aeba71bc862995cd302659f0e511ebe43ff28ee6df11fe8669b15dc1d)](https://dashboard.guardrails.io/gh/benfoster/repos/146839)\n\nMinid generates human-readable, URL-friendly, unique identifiers that are computed in-memory.\n\n- Safe without encoding (uses only characters from ASCII)\n- Avoids ambiguous characters (i/I/l/L/o/O/0)\n- Easy for humans to read and pronounce\n- Supports full UUID range (128 bits)\n- Safe for URLs and file names\n- Case-insensitive\n- 30% smaller than Guid's default string format\n- Supports formatting with prefixes\n\n### Example\n\nThe `Guid` `8108afcc-980f-438d-bdd7-51375fcf073a` converted to a Minid `Id` is encoded as `473cr1y0ghbyc3m1yfbwvn3nxx`.\n\nhttps://dotnetfiddle.net/jim7YP\n\n## Motivation\n\nThe motivation for this library came from a need to return readable \"resource\" identifiers in our APIs that could be computed quickly (no database lookups). In .NET the [Guid Struct](https://learn.microsoft.com/en-us/dotnet/api/system.guid?view=net-7.0) meets the uniqueness and computational requirements but its string representation is not optimal in terms of size and format.\n\nWith [Base32 encoding](https://en.wikipedia.org/wiki/Base32) we can encode a 128-bit `Guid` into a 26-character string. My original implementation was based on a Base32 encoder ported from [this Java implementation](https://github.com/google/google-authenticator-android/blob/master/java/com/google/android/apps/authenticator/util/Base32String.java).\n\nI later updated this to use [Kristian Hellang](https://github.com/khellang)'s \"CompactGuid\" implementation which was far more performant and had better support for ambiguous characters 🙇‍♂️\n\n## Usage\n\n```\ndotnet add package minid\n```\n\nYou can then use the provided `Id` struct to generate identifiers in your applications. \n\n```c#\npublic class Customer\n{\n    public Id Id { get; }\n\n    public Customer()\n    {\n        Id = Id.NewId();\n    }\n}\n```\n\nTo get the Base32-encoded value call `ToString()` on the `Id` value:\n\n```c#\nstring encoded = customer.Id.ToString();\n```\n\nYou can also initialise the `Id` type from an existing Guid:\n\n```c#\nvar existingId = new Id(existingGuid);\n```\n\nTo parse an encoded value:\n\n```c#\nif (Id.TryParse(encodedValue, out Id id))\n{\n\n}\n```\n\n### Prefixes\n\nA common pattern is to prefix API resource identifiers to indicate the resource type, for example `cus_473cr1y0ghbyc3m1yfbwvn3nxx` to represent a customer identifier. This is particularly useful when an API needs to accept identifiers for multiple resource types so that it can handle the request accordingly.\n\nTo provide a prefix when generating a new `Id`:\n\n```c#\nvar prefixedId = Id.NewId(prefix: \"cus\");\n```\n\nWhen specifying a prefix, the format of the encoded `Id` is `{prefix}_{encoded_guid_value}`.\n\nTo parse an encoded value you can optionally specify the prefix you expect. This is slightly more performant since you can benefit from defining your prefixes as a constant, avoiding an allocation:\n\n```c#\nconst string CustomerPrefix = \"cus\";\nif (Id.TryParse(encodedValue, CustomerPrefix, out Id id))\n{\n\n}\n```\n\nIf you don't provide a prefix and one exists, it will be detected by the presence of the `_` separator, but no prefix validation will be performed. \n\nThe implicit detection is required when needing to convert the values using a `TypeConverter` such as serializing and deserializing JSON (see below).\n\n**Note that currently the prefix is not considered when comparing two `Id` values.**\n\n### Serialization and Model-binding\n\nA `TypeConverter` and System.Text.Json `JsonConverter` are included so that you can bind, serialize and deserialize `Id` values without explicit conversion.\n\nNote that support for Type Converters was only added to [Newtonsoft Json.NET](https://www.newtonsoft.com/json) from version 10.\n\n### Why a dedicated type?\n\nWe originally started to use Base32-encoding to format our API resource identifiers in responses. Internally we used the underlying Guid value. This became problematic when correlating client and internal requests. Effectively we had introduced a second implicit identifier and our codebase was littered with conversion to/from the encoded values. \n\nIn our case it was far better to use the same encoded representation throughout our platform. Given we were mostly using document/no-sql databases which typically store Guid values as strings, we benefited from the reduction in size.\n\nIf you don't want to depend on this type you can use `string` but this will allocate more memory. My recommendation is to only convert to `string` when you need to.\n\n## Benchmarks\n\n```\nBenchmarkDotNet=v0.13.2, OS=macOS Monterey 12.5 (21G72) [Darwin 21.6.0]\nApple M1 Max, 1 CPU, 10 logical and 10 physical cores\n.NET SDK=6.0.400\n  [Host]     : .NET 6.0.8 (6.0.822.36306), Arm64 RyuJIT AdvSIMD\n  DefaultJob : .NET 6.0.8 (6.0.822.36306), Arm64 RyuJIT AdvSIMD\n```\n|     Method |      Mean |    Error |   StdDev |   Gen0 | Allocated |\n|----------- |----------:|---------:|---------:|-------:|----------:|\n|      NewId | 113.48 ns | 0.659 ns | 0.584 ns |      - |         - |\n| IdToString | 153.47 ns | 0.838 ns | 0.784 ns | 0.0381 |      80 B |\n|    ParseId |  63.32 ns | 0.547 ns | 0.511 ns |      - |         - |\n\nWith prefixes:\n\n|             Method |      Mean |    Error |   StdDev |   Gen0 | Allocated |\n|------------------- |----------:|---------:|---------:|-------:|----------:|\n|      NewPrefixedId | 111.78 ns | 0.437 ns | 0.387 ns |      - |         - |\n| PrefixedIdToString | 172.68 ns | 1.133 ns | 1.060 ns | 0.0420 |      88 B |\n|    ParsePrefixedId |  65.65 ns | 0.597 ns | 0.558 ns | 0.0153 |      32 B |\n| ParseIdKnownPrefix |  55.82 ns | 0.266 ns | 0.249 ns |      - |         - |\n\nNote that the `NewPrefixedId` benchmark makes uses of a constant for the prefix, hence the zero allocation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fminid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenfoster%2Fminid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenfoster%2Fminid/lists"}