109 lines
3.2 KiB
C#
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();
|
|
}
|
|
}
|
|
|