Editor/ActionBasedSerializedProperty.cs
using Facepunch.ActionGraphs;
using Sandbox;
using Sandbox.Internal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

namespace ExtendedBox;

public sealed class ActionBasedSerializedProperty : SerializedProperty
{
    public Func<SerializedProperty, SerializedObject>? PropertyToObject;

    private readonly string _name;

    private readonly string _title;

    private readonly string _description;

    private readonly string _groupName;

    private readonly string _sourceFile;

    private readonly int _sourceLine;

    private readonly Func<object> _get;

    private readonly Action<object> _set;

    private readonly List<Attribute> _attributes;

    private readonly SerializedObject _parent;

    public override SerializedObject Parent => _parent;

    public override bool IsMethod => false;

    public override string Name => _name;

    public override string DisplayName => _title;

    public override string Description => _description;

    public override string GroupName => _groupName;

    public override bool IsEditable => true;

    public override int Order => 0;

    public override Type PropertyType { get; }

    public override string SourceFile => _sourceFile;

    public override int SourceLine => _sourceLine;

    public override bool HasChanges => false;

    public override ref AsAccessor As => ref base.As;

    public ActionBasedSerializedProperty(Type type, string name, string title, string description, Func<object> get, Action<object> set, IEnumerable<Attribute> attributes, SerializedObject parent)
    {
        PropertyType = type;
        _name = name;
        _title = title;
        _description = description;
        _get = get;
        _set = set;
        _attributes = new List<Attribute>(attributes ?? []);
        _parent = parent;
        _groupName = _attributes.OfType<IGroupAttribute>().FirstOrDefault()?.Value ?? _groupName!;
        _groupName = _attributes.OfType<ICategoryProvider>().FirstOrDefault()?.Value ?? _groupName;
        _description = _attributes.OfType<IDescriptionAttribute>().FirstOrDefault()?.Value ?? _description;
        _title = _attributes.OfType<ITitleProvider>().FirstOrDefault()?.Value ?? _title;
        _sourceFile = _attributes.OfType<ISourcePathProvider>().FirstOrDefault()?.Path ?? _sourceFile!;
        _sourceLine = _attributes.OfType<ISourceLineProvider>().FirstOrDefault()?.Line ?? _sourceLine;
    }

    public override U GetValue<U>(U defaultValue = default!)
    {
        return ValueToType<U>(_get());
    }

    public override void SetValue<U>(U value)
    {
        _set(ValueToType(PropertyType, value)!);
    }

    public override IEnumerable<Attribute> GetAttributes()
    {
        return _attributes;
    }

    public override bool TryGetAsObject(out SerializedObject obj)
    {
        obj = PropertyToObject?.Invoke(this) ?? null!;
        return obj != null;
    }

    private object? ValueToType(Type targetType, object value, object? defaultValue = null)
    {
        try
        {
            if(value == null)
            {
                return GetDefaultValue();
            }

            if(value.GetType().IsAssignableTo(targetType))
                return value;

            if(targetType == typeof(string))
                return Convert.ChangeType($"{value}", targetType);

            if(value is string str)
                return JsonSerializer.Deserialize(str, targetType);


            if(targetType.IsEnum && value is IConvertible)
            {
                try
                {
                    return Enum.ToObject(targetType, Convert.ToInt64(value));
                }
                catch
                {
                    return GetDefaultValue();
                }
            }

            object obj2 = Convert.ChangeType(value, targetType);
            if(obj2 != null)
                return obj2;

            return JsonSerializer.SerializeToElement(value).Deserialize(targetType);
        }
        catch(Exception)
        {
            return GetDefaultValue();
        }

        object? GetDefaultValue()
        {
            if(defaultValue is null || !defaultValue.GetType().IsAssignableTo(targetType))
            {
                if(targetType.IsValueType)
                    return Activator.CreateInstance(targetType)!;
                return null;
            }

            return defaultValue;
        }
    }
}