From a4448b36a0f3944683376055fcb222253895a832 Mon Sep 17 00:00:00 2001 From: imskyyc Date: Sun, 28 Dec 2025 06:04:38 -0500 Subject: [PATCH 1/2] (fix) remove buggy checks from IsSnowflake (feature) add JSON conversion --- src/FlakeId.Tests/ParseTests.cs | 30 ++++++++++++++++ .../Extensions/FlakeIdJsonConverter.cs | 35 +++++++++++++++++++ src/FlakeId/Extensions/IdExtensions.cs | 4 +-- src/FlakeId/FlakeId.csproj | 4 +++ src/FlakeId/Id.cs | 2 ++ 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/FlakeId/Extensions/FlakeIdJsonConverter.cs diff --git a/src/FlakeId.Tests/ParseTests.cs b/src/FlakeId.Tests/ParseTests.cs index 6de8f66..16ee4f2 100644 --- a/src/FlakeId.Tests/ParseTests.cs +++ b/src/FlakeId.Tests/ParseTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text.Json; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace FlakeId.Tests @@ -73,5 +74,34 @@ public void Id_TryParse_Problematic() { Id id = Id.Parse(1108047973760811023); } + + public class IdJson + { + public required Id Id { get; set; } + } + + [TestMethod] + public void Id_JsonConverter_Read() + { + const string json = "{\"Id\":1108047973760811023}"; + + var obj = JsonSerializer.Deserialize(json); + + Assert.IsNotNull(obj); + Assert.AreEqual(1108047973760811023, obj.Id); + } + + [TestMethod] + public void Id_JsonConverter_Write() + { + var obj = new IdJson + { + Id = Id.Parse(1108047973760811023) + }; + + string json = JsonSerializer.Serialize(obj); + + Assert.AreEqual("{\"Id\":1108047973760811023}", json); + } } } diff --git a/src/FlakeId/Extensions/FlakeIdJsonConverter.cs b/src/FlakeId/Extensions/FlakeIdJsonConverter.cs new file mode 100644 index 0000000..0c970d2 --- /dev/null +++ b/src/FlakeId/Extensions/FlakeIdJsonConverter.cs @@ -0,0 +1,35 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace FlakeId.Extensions +{ + public class FlakeIdJsonConverter : JsonConverter + { + public override Id Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number && reader.TryGetInt64(out long value)) + { + return new Id(value); + } + + if (reader.TokenType != JsonTokenType.String) + { + throw new JsonException("Invalid JSON token for FlakeId.Id"); + } + + string stringValue = reader.GetString(); + if (stringValue != null && long.TryParse(stringValue, out long parsedValue)) + { + return new Id(parsedValue); + } + + throw new JsonException("Invalid JSON token for FlakeId.Id"); + } + + public override void Write(Utf8JsonWriter writer, Id value, JsonSerializerOptions options) + { + writer.WriteNumberValue(value); + } + } +} diff --git a/src/FlakeId/Extensions/IdExtensions.cs b/src/FlakeId/Extensions/IdExtensions.cs index 66c61b5..17b7f8f 100644 --- a/src/FlakeId/Extensions/IdExtensions.cs +++ b/src/FlakeId/Extensions/IdExtensions.cs @@ -38,11 +38,9 @@ public static bool IsSnowflake(this Id id) // The closest we can get is by decomposing its components, and ensuring all of them are set // to values that would be valid for a snowflake. long timestamp = id >> TimestampOffset; - long thread = (id >> ThreadOffset) & Id.ThreadIdMask; - long process = (id >> ProcessOffset) & Id.ProcessIdMask; long increment = id & Id.IncrementMask; - return timestamp > 0 && thread > 0 && process > 0 && increment >= 0; + return timestamp > 0 && increment >= 0; } /// diff --git a/src/FlakeId/FlakeId.csproj b/src/FlakeId/FlakeId.csproj index daaddca..7c1ed9a 100644 --- a/src/FlakeId/FlakeId.csproj +++ b/src/FlakeId/FlakeId.csproj @@ -36,4 +36,8 @@ + + + + diff --git a/src/FlakeId/Id.cs b/src/FlakeId/Id.cs index 924fef0..b825af1 100644 --- a/src/FlakeId/Id.cs +++ b/src/FlakeId/Id.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Threading; using FlakeId.Extensions; +using System.Text.Json.Serialization; namespace FlakeId { @@ -9,6 +10,7 @@ namespace FlakeId /// Represents a unique, K-ordered, sortable identifier. /// [DebuggerDisplay("{_value}")] + [JsonConverter(typeof(FlakeIdJsonConverter))] public struct Id : IComparable, IEquatable { // This implementation of Snowflake ID is based on the specification as published by Discord: From 11d4d30efa61bf8b605ee3648628d5cb58c0a437 Mon Sep 17 00:00:00 2001 From: imskyyc Date: Fri, 2 Jan 2026 19:58:34 -0500 Subject: [PATCH 2/2] fix .NET target and dependency version change Json Parser to parse to strings instead of numbers --- src/FlakeId.Tests/ParseTests.cs | 2 +- src/FlakeId/Extensions/FlakeIdJsonConverter.cs | 2 +- src/FlakeId/FlakeId.csproj | 7 +++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/FlakeId.Tests/ParseTests.cs b/src/FlakeId.Tests/ParseTests.cs index 16ee4f2..814d59c 100644 --- a/src/FlakeId.Tests/ParseTests.cs +++ b/src/FlakeId.Tests/ParseTests.cs @@ -101,7 +101,7 @@ public void Id_JsonConverter_Write() string json = JsonSerializer.Serialize(obj); - Assert.AreEqual("{\"Id\":1108047973760811023}", json); + Assert.AreEqual("{\"Id\":\"1108047973760811023\"}", json); } } } diff --git a/src/FlakeId/Extensions/FlakeIdJsonConverter.cs b/src/FlakeId/Extensions/FlakeIdJsonConverter.cs index 0c970d2..815628a 100644 --- a/src/FlakeId/Extensions/FlakeIdJsonConverter.cs +++ b/src/FlakeId/Extensions/FlakeIdJsonConverter.cs @@ -29,7 +29,7 @@ public override Id Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeria public override void Write(Utf8JsonWriter writer, Id value, JsonSerializerOptions options) { - writer.WriteNumberValue(value); + writer.WriteStringValue(value.ToString()); } } } diff --git a/src/FlakeId/FlakeId.csproj b/src/FlakeId/FlakeId.csproj index 7c1ed9a..0268d8a 100644 --- a/src/FlakeId/FlakeId.csproj +++ b/src/FlakeId/FlakeId.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net9.0 + netstandard2.0;net8.0 true Discord inspired, Twitter like implementation of Snowflake IDs, focused on performance. Snowflakes are 64 bit, highly optimized, decentralized, K-ordered, unique identifiers. https://github.com/aevitas/flakeid @@ -36,8 +36,7 @@ - - + + -