about summary refs log tree commit diff
path: root/LibMatrix/Extensions/CanonicalJsonSerializer.cs
blob: a6fbcf4894340fbe538da486f7486dca9778bea7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System.Collections.Frozen;
using System.Reflection;
using System.Security.Cryptography;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.Unicode;
using ArcaneLibs.Extensions;

namespace LibMatrix.Extensions;

public static class CanonicalJsonSerializer {
    // TODO: Alphabetise dictionaries
    private static JsonSerializerOptions _options => 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 = _options;
        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 System.Text.Json.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, _options);
    // public static String Serialize<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo) => JsonSerializer.Serialize(value, jsonTypeInfo, _options);
    // public static String Serialize(Object value, JsonTypeInfo jsonTypeInfo) 

#endregion

    private static partial 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]);
                }
            };
        }
    }
}