about summary refs log tree commit diff
path: root/LibMatrix/Extensions
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-06-20 04:50:00 +0200
committerRory& <root@rory.gay>2025-06-20 04:50:00 +0200
commit6975119b7d21cafdd0620d35b9542fb5d47ef392 (patch)
tree790c0b0ccb7e5d9245ae70f4951260803ead0b94 /LibMatrix/Extensions
parentStart of federation code (diff)
downloadLibMatrix-unpacked-6975119b7d21cafdd0620d35b9542fb5d47ef392.tar.xz
Basic federation, move some response classes to the right namespace
Diffstat (limited to 'LibMatrix/Extensions')
-rw-r--r--LibMatrix/Extensions/CanonicalJsonSerializer.cs96
1 files changed, 96 insertions, 0 deletions
diff --git a/LibMatrix/Extensions/CanonicalJsonSerializer.cs b/LibMatrix/Extensions/CanonicalJsonSerializer.cs

index 55a4b1a..ae535aa 100644 --- a/LibMatrix/Extensions/CanonicalJsonSerializer.cs +++ b/LibMatrix/Extensions/CanonicalJsonSerializer.cs
@@ -0,0 +1,96 @@ +using System.Collections.Frozen; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization.Metadata; +using ArcaneLibs.Extensions; + +namespace LibMatrix.Extensions; + +public static class CanonicalJsonSerializer { + // TODO: Alphabetise dictionaries + private static JsonSerializerOptions JsonOptions => new() { + WriteIndented = false, + Encoder = UnicodeJsonEncoder.Singleton, + }; + + private static readonly FrozenSet<PropertyInfo> JsonSerializerOptionsProperties = typeof(JsonSerializerOptions) + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.SetMethod != null && x.GetMethod != null) + .ToFrozenSet(); + + private static JsonSerializerOptions MergeOptions(JsonSerializerOptions? inputOptions) { + var newOptions = JsonOptions; + if (inputOptions == null) + return newOptions; + + foreach (var property in JsonSerializerOptionsProperties) { + if(property.Name == nameof(JsonSerializerOptions.Encoder)) + continue; + if (property.Name == nameof(JsonSerializerOptions.WriteIndented)) + continue; + + var value = property.GetValue(inputOptions); + // if (value == null) + // continue; + property.SetValue(newOptions, value); + } + + return newOptions; + } + +#region STJ API + + public static String Serialize<TValue>(TValue value, JsonSerializerOptions? options = null) { + var newOptions = MergeOptions(options); + + return JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + .SortProperties()! + .CanonicalizeNumbers()! + .ToJsonString(newOptions); + + + // System.Text.Json.JsonSerializer.SerializeToNode(System.Text.Json.JsonSerializer.Deserialize<dynamic>("{\n \"a\": -0,\n \"b\": 1e10\n}")).ToJsonString(); + + } + + public static String Serialize(object value, Type inputType, JsonSerializerOptions? options = null) => JsonSerializer.Serialize(value, inputType, JsonOptions); + // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options); + // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) + + public static byte[] SerializeToUtf8Bytes<T>(T value, JsonSerializerOptions? options = null) { + var newOptions = MergeOptions(null); + return JsonSerializer.SerializeToNode(value, options) // We want to allow passing custom converters for eg. double/float -> string here... + .SortProperties()! + .CanonicalizeNumbers()! + .ToJsonString(newOptions).AsBytes().ToArray(); + } + +#endregion + + // ReSharper disable once UnusedType.Local + private static class JsonExtensions { + public static Action<JsonTypeInfo> AlphabetizeProperties(Type type) { + return typeInfo => { + if (typeInfo.Kind != JsonTypeInfoKind.Object || !type.IsAssignableFrom(typeInfo.Type)) + return; + AlphabetizeProperties()(typeInfo); + }; + } + + public static Action<JsonTypeInfo> AlphabetizeProperties() { + return static typeInfo => { + if (typeInfo.Kind == JsonTypeInfoKind.Dictionary) { } + + if (typeInfo.Kind != JsonTypeInfoKind.Object) + return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + } +} \ No newline at end of file