48 lines
1.6 KiB
C#
48 lines
1.6 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using static StellaOps.Localization.T;
|
|
|
|
namespace StellaOps.Canonicalization.Json;
|
|
|
|
/// <summary>
|
|
/// Produces canonical JSON output with deterministic ordering.
|
|
/// Implements RFC 8785 principles for stable output.
|
|
/// </summary>
|
|
public static class CanonicalJsonSerializer
|
|
{
|
|
private static readonly JsonSerializerOptions _options = new()
|
|
{
|
|
WriteIndented = false,
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
|
NumberHandling = JsonNumberHandling.Strict,
|
|
Converters =
|
|
{
|
|
new StableDictionaryConverterFactory(),
|
|
new Iso8601DateTimeConverter()
|
|
}
|
|
};
|
|
|
|
public static string Serialize<T>(T value)
|
|
=> JsonSerializer.Serialize(value, _options);
|
|
|
|
public static (string Json, string Digest) SerializeWithDigest<T>(T value)
|
|
{
|
|
var json = Serialize(value);
|
|
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(json));
|
|
var digest = Convert.ToHexString(hash).ToLowerInvariant();
|
|
return (json, digest);
|
|
}
|
|
|
|
public static T Deserialize<T>(string json)
|
|
{
|
|
return JsonSerializer.Deserialize<T>(json, _options)
|
|
?? throw new InvalidOperationException(_t("common.canonicalization.deserialize_failed", typeof(T).Name));
|
|
}
|
|
}
|