// -----------------------------------------------------------------------------
// 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;
///
/// JSON converter for HlcTimestamp using the sortable string format.
///
///
/// Serializes HlcTimestamp to/from the sortable string format (e.g., "1704067200000-scheduler-east-1-000042").
/// This format is both human-readable and lexicographically sortable.
///
public sealed class HlcTimestampJsonConverter : JsonConverter
{
///
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);
}
}
///
public override void Write(Utf8JsonWriter writer, HlcTimestamp value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToSortableString());
}
}
///
/// JSON converter for nullable HlcTimestamp.
///
public sealed class NullableHlcTimestampJsonConverter : JsonConverter
{
private readonly HlcTimestampJsonConverter _inner = new();
///
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);
}
///
public override void Write(Utf8JsonWriter writer, HlcTimestamp? value, JsonSerializerOptions options)
{
if (!value.HasValue)
{
writer.WriteNullValue();
return;
}
_inner.Write(writer, value.Value, options);
}
}
///
/// JSON converter for HlcTimestamp using object format with individual properties.
///
///
/// Alternative converter that serializes HlcTimestamp as an object:
///
/// {
/// "physicalTime": 1704067200000,
/// "nodeId": "scheduler-east-1",
/// "logicalCounter": 42
/// }
///
/// Use this when you need to query individual fields in JSON storage.
///
public sealed class HlcTimestampObjectJsonConverter : JsonConverter
{
///
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
};
}
///
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();
}
}