Casting SerializedProperty to the desired type

Hello. I am writing a custom editor and I need to get a private field of type (HealthData). For this, I use the FindProperty method, where I get the SerializedProperty variable. But I can’t figure out how I can cast it to the correct HealthData type. Can you please tell me how to do the type casting correctly?

You can’t cast it. The SerializedProperty is not some casted version of the object. Rather instead it’s a reference to the serialized representation of the object.

Note the serialized representation and the object itself are 2 distinctly different things. One is the yaml (string data) that can be used to create an object and set its property (that’s the serialized representation), and the other is the actual object.

This is why you have to call things like “ApplyModifiedProperties” to apply the changes to the serializedobject/property onto the object.

Now, you can get a reference to it, but you’ll have to use reflection to get to it.

You have to start at the SerializedObject.targetObject. Then you take the SerializedProperty.propertyPath and split it by the ‘.’ per property (note special handling has to be done to deal with arrays and how unity formats that into the ‘path’). And then reflect down to the object.

Big issue is you need to make sure your SerializedObject isn’t in multi-target mode. Check the SerializedObject.isEditingMultipleObjects to tell. If you are then targetObject only points at the first of the objects, not all of them.

Here’s some code I use to do it:

        /// <summary>
        /// Gets the object the property represents.
        /// </summary>
        /// <param name="prop"></param>
        /// <returns></returns>
        public static object GetTargetObjectOfProperty(SerializedProperty prop)
        {
            var path = prop.propertyPath.Replace(".Array.data[", "[");
            object obj = prop.serializedObject.targetObject;
            var elements = path.Split('.');
            foreach (var element in elements)
            {
                if (element.Contains("["))
                {
                    var elementName = element.Substring(0, element.IndexOf("["));
                    var index = System.Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
                    obj = GetValue_Imp(obj, elementName, index);
                }
                else
                {
                    obj = GetValue_Imp(obj, element);
                }
            }
            return obj;
        }

        private static object GetValue_Imp(object source, string name)
        {
            if (source == null)
                return null;
            var type = source.GetType();

            while (type != null)
            {
                var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                if (f != null)
                    return f.GetValue(source);

                var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (p != null)
                    return p.GetValue(source, null);

                type = type.BaseType;
            }
            return null;
        }

        private static object GetValue_Imp(object source, string name, int index)
        {
            var enumerable = GetValue_Imp(source, name) as System.Collections.IEnumerable;
            if (enumerable == null) return null;
            var enm = enumerable.GetEnumerator();
            //while (index-- >= 0)
            //    enm.MoveNext();
            //return enm.Current;

            for (int i = 0; i <= index; i++)
            {
                if (!enm.MoveNext()) return null;
            }
            return enm.Current;
        }

note - there is some naivete to this as it doesn’t test if the field is serializable. This create a weird edge case where if you have a private serialized field in some class and then inherit from it and have a non-serialized property named exactly the same thing, this will return the wrong target.

To fix this in the GetValue you’d have to check if the field is private, and if it it you’ll need to make sure it contains the UnityEngine.SerializeFieldAttribute or not.

Thing is, it’s such a weird edge case that you likely won’t ever run into it.

4 Likes

Thank you for such an extensive and detailed answer.