Need some help serializing Ardalis' Smart Enum package / Is this even possible?

I am trying to serialize the SmartEnum class provided by ardalis at his repo GitHub - ardalis/SmartEnum: A base class for quickly and easily creating strongly typed enum replacements in C#. in Unity.

What I am trying to achieve: Similar functionality to the c# enum where the serialized value has a property drawer with a drop down of available options. When de-serialized the reference of the object needs to be reassigned to the correct value.

The syntax for defining a SmartEnum is as follows:

using Ardalis.SmartEnum;

public sealed class TestEnum : SmartEnum<TestEnum>
{
    public static readonly TestEnum One = new TestEnum(nameof(One), 1);
    public static readonly TestEnum Two = new TestEnum(nameof(Two), 2);
    public static readonly TestEnum Three = new TestEnum(nameof(Three), 3);

    private TestEnum(string name, int value) : base(name, value)
    {
    }
}

You would then define an enum field as such:

TestEnum myTestEnum = TestEnum.One;

In order to achieve this I have created a new class that extends SmartEnum and implements ISerializationCallbackReceiver.

I have gotten quite far but have been struggling with the deserialization. So before I give up entirely I’m wondering if its even possible since you can’t normally serialize static fields. I am using reflection to get a list of the name values of all of the SmartEnums declared statically then serializing a string which is what the property drawer modifies. On deserialization I try to update the original ref and ideally swap it to the memory location of a different statically declared instance. This is where it all falls flat as I cannot access/change the reference that was originally assigned to with TestEnum myTestEnum = TestEnum.One;.

Here is what I have so far

    public class UnitySmartEnum<T> : SmartEnum<T>, ISerializationCallbackReceiver where T : SmartEnum<T,int>
    {
        public UnitySmartEnum(string name,int value) : base(name,value) { }

        //Custom serialization
       
        [SerializeField]
        List<string> options;

        [SerializeField]
        string value;

        public void OnBeforeSerialize()
        {
            value = Name;

            options = GetType()
                .GetFields(BindingFlags.Public | BindingFlags.Static)
                .Where(f => f.FieldType == typeof(T))
                .Select(fieldInfo => fieldInfo.GetValue(null))
                .Cast<UnitySmartEnum<T>>()
                .Select(e => e.Name)
                .ToList();
        }

        public void OnAfterDeserialize()
        {
            var listOptions = GetType()
                .GetFields(BindingFlags.Public | BindingFlags.Static)
                .Where(f => f.FieldType == typeof(T))
                .Select(fieldInfo => fieldInfo.GetValue(null))
                .Cast<UnitySmartEnum<T>>();

            var tryGetSelectedValue = listOptions.Where(option => option.Name == value).FirstOrDefault();


            //reassignment is where it all goes wrong since the original instance is static and readonly, I want to change the instance that the reference points to I left this here even though it doesn't work so you can see my last ditch attempts to make it work
            if (tryGetSelectedValue != null)
            {
                var me = this;
                Reassign(ref me,ref tryGetSelectedValue);
            }
            else
            {
                var me = this;
                tryGetSelectedValue = listOptions.FirstOrDefault();
                Reassign(ref me,ref tryGetSelectedValue);
            }
        }

        public static void Reassign(ref UnitySmartEnum<T> a, ref UnitySmartEnum<T> b)
        {
            a = b;
        }
    }

    [CustomPropertyDrawer(typeof(RPGEvents))]
    public class EnumPropertyDrawer : PropertyDrawer
    {
        List<string> options = new List<string>();
        int selectedIndex = 0;

        public override VisualElement CreatePropertyGUI(SerializedProperty property)
        {
            SerializedProperty enumNames = property.FindPropertyRelative("options");
            SerializedProperty name = property.FindPropertyRelative("value");

            int i = 0;
            options.Clear();
            foreach (SerializedProperty item in enumNames)
            {
                options.Add(item.stringValue);
                if (item.stringValue == name.stringValue)
                {
                    selectedIndex = i;
                }
                i++;
            }

            var container = new VisualElement();
            var nameField = new DropdownField("Enum Value",options,selectedIndex);
            nameField.RegisterValueChangedCallback((e) =>
            {
                name.stringValue = e.newValue;
                property.serializedObject.ApplyModifiedProperties();
            });

            container.Add(nameField);

            return container;
        }
    }

I’ve never worked with Unity’s serialization or property drawers before so there might be obvious mistakes.

We implemented this sort of deserialization-into-known-object by making a JsonConverter for the type we had in mind, then just saving it as the raw string resource name you would use with Resources.Load() out of the in-project array of pre-made entities, in this case a ScriptableObject.

Not sure if that could help above, but it’s the simplest way to do proxy-saves of un-serializable data blobs.

As for enums themselves…

Enums are bad in Unity3D if you intend them to be serialized:

It is much better to use ScriptableObjects for many enumerative uses. You can even define additional associated data with each one of them, and drag them into other parts of your game (scenes, prefabs, other ScriptableObjects) however you like. References remain rock solid even if you rename them, reorder them, reorganize them, etc. They are always connected via the meta file GUID.

Collections / groups of ScriptableObjects can also be loaded en-masse with calls such as Resources.LoadAll<T>().

Best of all, Unity already gives you a built-in filterable picker when you click on the little target dot to the right side of a field of any given type… bonus!

Seems like that would be a lot easier and Resources.LoadAll<T> is definitely something I will be using for loading objects definitions etc into memory. As for using them to represent enums I am not sure exactly how that would work. I guess you could just have a list of strings, but that would lead to indexing issues. If you defined constant values then I am not sure what happens to objects already on disk if you add a new constant.

For this particular use I am looking for a solution I can use generally moving forward and want to avoid enums. One of the things that drew me to SmartEnum is its ability to implement functionality similar to the [Flag] attribute for enums where you can define a bit field from an enum. This is something that would be very useful to me in a number of cases.

I also have written a custom event system based on the Node.js EventEmitter. Currently it uses strings to hash the event handlers in a dictionary. As you can imagine this can quickly lead to call back hell so it would be better to use something where typos aren’t a problem and I can track references to see more easily who is subscribed to what. I was thinking of using SmartEnum for that.

Currently I would write something like:

EventEmitter emitter = new EventEmitter();

emitter.Subscribe("OnAttackStart", (sender, args) => {
   Debug.Log("Attack has started");
});

//... elsewhere

EventEmitter emitter = new EventEmitter();
emitter.Emit("OnAttackStart", this, new EventArgs());

But I want to use constants rather than strings.

I will look into json since the package does have an optional json serializer. Can unity serialize json into the editor? I am not sure what format it uses natively for things like scriptable object is it YAML?

How did you manage to add it to Unity? For me it throws bunch of errors.