Files
git.stella-ops.org/src/Scanner/StellaOps.Scanner.WebService/Serialization/DeterministicCborSerializer.cs
StellaOps Bot 6410a6d082 up
2025-12-18 20:37:27 +02:00

109 lines
3.2 KiB
C#

using System.Collections.Generic;
using System.Formats.Cbor;
using System.Text;
using System.Text.Json;
namespace StellaOps.Scanner.WebService.Serialization;
internal static class DeterministicCborSerializer
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
WriteIndented = false
};
public static byte[] Serialize<T>(T value)
{
using var document = JsonSerializer.SerializeToDocument(value, JsonOptions);
var writer = new CborWriter(CborConformanceMode.Canonical);
WriteElement(writer, document.RootElement);
return writer.Encode();
}
private static void WriteElement(CborWriter writer, JsonElement element)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
WriteObject(writer, element);
return;
case JsonValueKind.Array:
writer.WriteStartArray(element.GetArrayLength());
foreach (var item in element.EnumerateArray())
{
WriteElement(writer, item);
}
writer.WriteEndArray();
return;
case JsonValueKind.String:
writer.WriteTextString(element.GetString() ?? string.Empty);
return;
case JsonValueKind.Number:
if (element.TryGetInt64(out var int64))
{
writer.WriteInt64(int64);
return;
}
writer.WriteDouble(element.GetDouble());
return;
case JsonValueKind.True:
writer.WriteBoolean(true);
return;
case JsonValueKind.False:
writer.WriteBoolean(false);
return;
case JsonValueKind.Null:
case JsonValueKind.Undefined:
writer.WriteNull();
return;
default:
writer.WriteNull();
return;
}
}
private static void WriteObject(CborWriter writer, JsonElement element)
{
var properties = new List<(byte[] KeyBytes, string Key, JsonElement Value)>();
foreach (var property in element.EnumerateObject())
{
var keyBytes = Encoding.UTF8.GetBytes(property.Name);
properties.Add((keyBytes, property.Name, property.Value));
}
properties.Sort(static (left, right) =>
{
var lengthCompare = left.KeyBytes.Length.CompareTo(right.KeyBytes.Length);
if (lengthCompare != 0)
{
return lengthCompare;
}
for (var i = 0; i < left.KeyBytes.Length; i++)
{
var byteCompare = left.KeyBytes[i].CompareTo(right.KeyBytes[i]);
if (byteCompare != 0)
{
return byteCompare;
}
}
return 0;
});
writer.WriteStartMap(properties.Count);
foreach (var (_, key, value) in properties)
{
writer.WriteTextString(key);
WriteElement(writer, value);
}
writer.WriteEndMap();
}
}