// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Core.Serialization.JsonConverters
{
using System;
using System.Buffers.Binary;
using System.Text.Json;
using System.Text.Json.Serialization;
using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters;
///
/// Serializes WGuid values as canonical Guid strings while remaining tolerant of legacy payloads.
///
public sealed class WGuidConverter : JsonConverter
{
public static readonly WGuidConverter Instance = new();
private WGuidConverter() { }
public override WGuid Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
if (reader.TokenType == JsonTokenType.String)
{
string value = reader.GetString();
if (string.IsNullOrEmpty(value))
{
return WGuid.Empty;
}
if (WGuid.TryParse(value, out WGuid parsed))
{
return parsed;
}
throw new JsonException($"Invalid {nameof(WGuid)} string value.");
}
if (reader.TokenType == JsonTokenType.StartObject)
{
return ReadFromObject(ref reader);
}
if (reader.TokenType == JsonTokenType.Null)
{
return WGuid.Empty;
}
throw new JsonException($"{nameof(WGuid)} must be encoded as a JSON string.");
}
public override void Write(
Utf8JsonWriter writer,
WGuid value,
JsonSerializerOptions options
)
{
writer.WriteStringValue(value.ToString());
}
private static WGuid ReadFromObject(ref Utf8JsonReader reader)
{
long low = 0L;
long high = 0L;
bool sawLow = false;
bool sawHigh = false;
string guidString = null;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
if (!string.IsNullOrEmpty(guidString))
{
if (WGuid.TryParse(guidString, out WGuid parsed))
{
return parsed;
}
throw new JsonException(
$"Invalid {nameof(Guid)} property for {nameof(WGuid)}."
);
}
if (sawLow && sawHigh)
{
Span buffer = stackalloc byte[16];
BinaryPrimitives.WriteUInt64LittleEndian(
buffer.Slice(0, 8),
unchecked((ulong)low)
);
BinaryPrimitives.WriteUInt64LittleEndian(
buffer.Slice(8, 8),
unchecked((ulong)high)
);
return new WGuid(new Guid(buffer));
}
return WGuid.Empty;
}
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException(
$"Unexpected token while reading {nameof(WGuid)} object."
);
}
if (reader.ValueTextEquals(WGuid.LowFieldName))
{
reader.Read();
low = reader.GetInt64();
sawLow = true;
}
else if (reader.ValueTextEquals(WGuid.HighFieldName))
{
reader.Read();
high = reader.GetInt64();
sawHigh = true;
}
else if (reader.ValueTextEquals(WGuid.GuidPropertyName))
{
reader.Read();
if (reader.TokenType == JsonTokenType.String)
{
guidString = reader.GetString();
}
else
{
throw new JsonException(
$"{WGuid.GuidPropertyName} property on {nameof(WGuid)} must be a string."
);
}
}
else
{
reader.Skip();
}
}
throw new JsonException(
$"Unexpected end of JSON while reading {nameof(WGuid)} object."
);
}
}
}