Files
git.stella-ops.org/src/__Libraries/StellaOps.HybridLogicalClock/HlcTimestampJsonConverter.cs

176 lines
5.6 KiB
C#

// -----------------------------------------------------------------------------
// HlcTimestampJsonConverter.cs
// Sprint: SPRINT_20260105_002_001_LB_hlc_core_library
// Task: HLC-006 - Add HlcTimestampJsonConverter for System.Text.Json serialization
// -----------------------------------------------------------------------------
using System.Text.Json;
using System.Text.Json.Serialization;
namespace StellaOps.HybridLogicalClock;
/// <summary>
/// JSON converter for HlcTimestamp using the sortable string format.
/// </summary>
/// <remarks>
/// Serializes HlcTimestamp to/from the sortable string format (e.g., "1704067200000-scheduler-east-1-000042").
/// This format is both human-readable and lexicographically sortable.
/// </remarks>
public sealed class HlcTimestampJsonConverter : JsonConverter<HlcTimestamp>
{
/// <inheritdoc/>
public override HlcTimestamp Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
throw new JsonException("Cannot deserialize null to HlcTimestamp. Use HlcTimestamp? with NullableHlcTimestampJsonConverter for nullable timestamps.");
}
if (reader.TokenType != JsonTokenType.String)
{
throw new JsonException($"Expected string but got {reader.TokenType}");
}
var value = reader.GetString();
if (string.IsNullOrWhiteSpace(value))
{
throw new JsonException("Cannot convert empty string to HlcTimestamp");
}
try
{
return HlcTimestamp.Parse(value);
}
catch (FormatException ex)
{
throw new JsonException($"Invalid HlcTimestamp format: {value}", ex);
}
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, HlcTimestamp value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToSortableString());
}
}
/// <summary>
/// JSON converter for nullable HlcTimestamp.
/// </summary>
public sealed class NullableHlcTimestampJsonConverter : JsonConverter<HlcTimestamp?>
{
private readonly HlcTimestampJsonConverter _inner = new();
/// <inheritdoc/>
public override HlcTimestamp? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
return _inner.Read(ref reader, typeof(HlcTimestamp), options);
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, HlcTimestamp? value, JsonSerializerOptions options)
{
if (!value.HasValue)
{
writer.WriteNullValue();
return;
}
_inner.Write(writer, value.Value, options);
}
}
/// <summary>
/// JSON converter for HlcTimestamp using object format with individual properties.
/// </summary>
/// <remarks>
/// Alternative converter that serializes HlcTimestamp as an object:
/// <code>
/// {
/// "physicalTime": 1704067200000,
/// "nodeId": "scheduler-east-1",
/// "logicalCounter": 42
/// }
/// </code>
/// Use this when you need to query individual fields in JSON storage.
/// </remarks>
public sealed class HlcTimestampObjectJsonConverter : JsonConverter<HlcTimestamp>
{
/// <inheritdoc/>
public override HlcTimestamp Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException($"Expected StartObject but got {reader.TokenType}");
}
long? physicalTime = null;
string? nodeId = null;
int? logicalCounter = null;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException($"Expected PropertyName but got {reader.TokenType}");
}
var propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "physicalTime":
case "PhysicalTime":
physicalTime = reader.GetInt64();
break;
case "nodeId":
case "NodeId":
nodeId = reader.GetString();
break;
case "logicalCounter":
case "LogicalCounter":
logicalCounter = reader.GetInt32();
break;
default:
reader.Skip();
break;
}
}
if (!physicalTime.HasValue)
throw new JsonException("Missing required property 'physicalTime'");
if (string.IsNullOrEmpty(nodeId))
throw new JsonException("Missing required property 'nodeId'");
if (!logicalCounter.HasValue)
throw new JsonException("Missing required property 'logicalCounter'");
return new HlcTimestamp
{
PhysicalTime = physicalTime.Value,
NodeId = nodeId,
LogicalCounter = logicalCounter.Value
};
}
/// <inheritdoc/>
public override void Write(Utf8JsonWriter writer, HlcTimestamp value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteNumber("physicalTime", value.PhysicalTime);
writer.WriteString("nodeId", value.NodeId);
writer.WriteNumber("logicalCounter", value.LogicalCounter);
writer.WriteEndObject();
}
}