Any.cs
using Sandbox;
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ExtendedEditor;

[JsonConverter(typeof(Any.AnyJsonConverterFactory))]
public struct Any<T>
{
    public T Value { get; set; }

    public Any() : this(default!)
    {
    }

    public Any(T value)
    {
        Value = value;
    }
}

public static class Any
{
    public class AnyJsonConverterFactory : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert) => typeToConvert.IsGenericType && typeof(Any<>) == TypeLibrary.GetType(typeToConvert).TargetType;
        public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var type = TypeLibrary.GetType(typeof(AnyJsonConverter<>)).MakeGenericType(TypeLibrary.GetGenericArguments(typeToConvert));
            return TypeLibrary.Create<JsonConverter>(type);
        }
    }

    public class AnyJsonConverter<T> : JsonConverter<Any<T>>
    {
        [SkipHotload]
        private static JsonSerializerOptions? _typeConverterOptions;

        public override Any<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if(reader.TokenType == JsonTokenType.Null)
            {
                if(typeToConvert.IsValueType && TypeLibrary.GetType(typeToConvert).TargetType != typeof(Nullable<>))
                    Log.Warning($"Can't convert {JsonTokenType.Null} to {typeToConvert.FullName}");

                return default;
            }

            Type? type = null;
            JsonDocument? valueNode = null;
            try
            {
                if(reader.TokenType != JsonTokenType.StartObject)
                    throw new InvalidOperationException($"Token {reader.TokenType} is not expected. Expected {JsonTokenType.StartObject}.");

                while(reader.Read() && reader.TokenType != JsonTokenType.EndObject)
                {
                    if(reader.TokenType != JsonTokenType.PropertyName)
                        throw new InvalidOperationException($"Token {reader.TokenType} is not expected. Expected {JsonTokenType.PropertyName}.");

                    var propertyName = reader.ValueSpan;

                    if(propertyName.SequenceEqual("$type"u8))
                    {
                        if(_typeConverterOptions is null)
                        {
                            _typeConverterOptions = new(options);
                            _typeConverterOptions.Converters.Clear();
                            _typeConverterOptions.Converters.Add(new TypeJsonConverter());
                        }

                        reader.Read();
                        type = JsonSerializer.Deserialize<Type>(ref reader, _typeConverterOptions);
                    }
                    else if(propertyName.SequenceEqual("$value"u8))
                    {
                        reader.Read();
                        valueNode = JsonDocument.ParseValue(ref reader);
                    }
                    else
                    {
                        reader.Skip();
                    }
                }

                if(reader.TokenType != JsonTokenType.EndObject)
                    throw new InvalidOperationException($"Token {reader.TokenType} is not expected. Expected {JsonTokenType.EndObject}.");

                if(type is null)
                {
                    Log.Warning($"Type not found by converter {typeof(JsonConverter).FullName} when converting value.");
                    return default;
                }

                if(valueNode is null)
                {
                    Log.Warning($"Value node not found by converter {typeof(JsonConverter).FullName} when converting value.");
                    return default;
                }

                var value = JsonSerializer.Deserialize(valueNode, type, options);
                if(value is not T t)
                {
                    Log.Warning($"Value {value} is not of type {typeof(T)}.");
                    return default;
                }

                return new Any<T>(t);
            }
            finally
            {
                valueNode?.Dispose();
            }
        }

        public override void Write(Utf8JsonWriter writer, Any<T> obj, JsonSerializerOptions options)
        {
            var value = obj.Value;
            if(value is null)
            {
                writer.WriteNullValue();
                return;
            }

            if(_typeConverterOptions is null)
            {
                _typeConverterOptions = new(options);
                _typeConverterOptions.Converters.Clear();
                _typeConverterOptions.Converters.Add(new TypeJsonConverter());
            }

            writer.WriteStartObject();
            writer.WritePropertyName("$type");
            JsonSerializer.Serialize(writer, value.GetType(), _typeConverterOptions);
            writer.WritePropertyName("$value");
            JsonSerializer.Serialize(writer, value, value.GetType(), options);
            writer.WriteEndObject();
        }
    }
}