A smarter Way to get the Type of SerializedProperty

Hey Folks. I used the DrawDefaultInspectorWithoutScriptField by JAKJ and it gave me kind of the Idea of creating a custom Inspector, that could inject content where I want without writing everything from anew.

So now I wanted to add some functionality to the scriptableObject Fields, among them, creating a
new one.
Problem is, the most promising value I can get to figure out a serializedProperty’s Object’s type is serializedProperty.type, which is a string, giving me this: PPtr<$ScriptableObject>

So I deleted PPtr<$> and used the remaining string to use type.getType(string) with another ScriptableObject’s reflected Assembly.

string str =  serializedProperty.type.Replace("PPtr<$", "").Replace(">", "");
string other = "Namespace." + str + ", " + typeof(OtherScriptableObject).Assembly;
Type type = Type.GetType(other);

However, this is not safe for Scriptable Objects inside Namespaces (and I think classes), so my Question is, is there something more appropriate to get the Type of a serializedProperty’s Object or Do I have to politely ask the Dev Team for a more sophisticated indicator?^^

1 Like

NOTE: if you have a PropertyDrawr reference, or are creating a custom one- this is probably NOT the answer for you- check out CP’s answer below. This answer is for those who do NOT have a PropertyDrawer to reference.


Here is how I both get the types of SerializedProperties, AND get /set the values as object’s

    public static object GetValue(this SerializedProperty property)
    {
        System.Type parentType = property.serializedObject.targetObject.GetType();
        System.Reflection.FieldInfo fi = parentType.GetField(property.propertyPath);  
        return fi.GetValue(property.serializedObject.targetObject);
    }

    public static void SetValue(this SerializedProperty property,object value)
    {
        System.Type parentType = property.serializedObject.targetObject.GetType();
        System.Reflection.FieldInfo fi = parentType.GetField(property.propertyPath);//this FieldInfo contains the type.
        fi.SetValue(property.serializedObject.targetObject, value);
    }

To extract the type from the FieldInfo class use the FieldType member (FieldInfo.FieldType Property (System.Reflection) | Microsoft Learn)

 public static System.Type GetType(SerializedProperty property)
        {
            System.Type parentType = property.serializedObject.targetObject.GetType();
            System.Reflection.FieldInfo fi = parentType.GetField(property.propertyPath);
            return fi.FieldType;
        }

EDIT/UPDATE:

While dealing with multiple “levels” of properties, I found the above functions did NOT work as expected and returned null for any propertyPath (a member of the property parameter) that included a dot ‘.’.

Replace above instances of GetField with GetFieldViaPath (the function below) to account for, and handle, paths that are multiple members deep.

 public static System.Reflection.FieldInfo GetFieldViaPath(this System.Type type,string path)
    {
        System.Type parentType = type;
        System.Reflection.FieldInfo fi = type.GetField(path);
        string[] perDot = path.Split('.');
        foreach (string fieldName in perDot)
        {
            fi = parentType.GetField(fieldName);
            if (fi != null)
                parentType = fi.FieldType;
            else
                return null;
        }
        if (fi != null)
            return fi;
        else return null;
    }

As this thread is still kind of active and seems to be pretty high in google’s ranking:

Just use PropertyDrawer’s class member: fieldInfo.FieldType to get the type.

That’s it.

Why no one gave an answer why SerializedProperty.type does works like that? In 2022.3.7f1, this is still a problem. These codes wont work for property fields that has a automatic backing field. Which means even more complexity.

You need to add Array

if (parentType.IsArray) { parentType
= parentType.GetElementType(); }

and add private field in the function “GetField”

BindingFlags flags =
BindingFlags.Instance|BindingFlags.Public
| BindingFlags.NonPublic;

It’s based on @Glurth post but also consider the multiple “level” of properties may contain array and generic list:

    public static class TypeExtension {
        public static System.Reflection.FieldInfo GetFieldViaPath (this System.Type type,
            string path) {
            var parent = type;
            var fi = parent.GetField (path);
            var paths = path.Split ('.');

            for (int i = 0; i < paths.Length; i++) {
                fi = parent.GetField (paths*);*

// there are only two container field type that can be serialized:
// Array and List
if (fi.FieldType.IsArray) {
parent = fi.FieldType.GetElementType ();
i += 2;
continue;
}

if (fi.FieldType.IsGenericType) {
parent = fi.FieldType.GetGenericArguments () [0];
i += 2;
continue;
}

if (fi != null) {
parent = fi.FieldType;
} else {
return null;
}

}

return fi;
}
}
However, if the return type has derived classes. It can be more complicated.
class A:ScritableObject { }

class B : A { }

class MyClass{
A a1 = new B();
}
If we want to retrieve the type of a1, the GetType() we talked above will return the type of A which is the base class. Because we are not using the memberinfo of instance to retrieving type.
So we need the real instance to get the real type. But the instance is dynamically assigned or is depend on your object reference on the inspector. Here is my not perfect solution ( If you use the new UIElement, you should registe soem value-change event instead):
var type = GetType (yourProperty);
if (yourProperty.propertyType.Equals(UnityEditor.SerializedPropertyType.ObjectReference) && yourProperty.objectReferenceValue != null)
type = dn.objectReferenceValue.GetType ();

It’s only useful when you don’t know the specific property. For example, when iterating all property. But if you already know it. You can get rid of these reflection operations and get type directly by its value.
Remember it’s expensive to do reflection every time (Congratulations, if you also use IMGUI), and unity editor provides serializedobject and serializedProperty which are internal type to cach these info.
----------

public static System.Reflection.FieldInfo GetFieldViaPath(this System.Type type, string path)
{
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var parent = type;
var fi = parent.GetField(path, flags);
var paths = path.Split(‘.’);

        for (int i = 0; i < paths.Length; i++)
        {
            fi = parent.GetField(paths*, flags);*

// there are only two container field type that can be serialized:
// Array and List
if (fi.FieldType.IsArray)
{
parent = fi.FieldType.GetElementType();
i += 2;
continue;
}

if (fi.FieldType.IsGenericType)
{
parent = fi.FieldType.GetGenericArguments()[0];
i += 2;
continue;
}

if (fi != null)
{
parent = fi.FieldType;
}
else
{
return null;
}

}

return fi;
}
First I wanna thank you all here.
I Did this new mixing solution of @bremyBBW and the last iteration of @Castle24 to fix the error I got with a private, but serialized, field of mine.

If you want to handle inheritance:

 public static System.Reflection.FieldInfo GetFieldViaPath(this System.Type type, string path)
 {
    			var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
    			var parent = type;
    			var fi = parent.GetField(path, flags);
    			var paths = path.Split('.');
    
    			for (int i = 0; i < paths.Length; i++)
    			{
    				fi = parent.GetField(paths*, flags);*
  •  		if (fi != null)*
    
  •  		{*
    
  •  			// there are only two container field type that can be serialized:*
    
  •  			// Array and List<T>*
    
  •  			if (fi.FieldType.IsArray)*
    
  •  			{*
    
  •  				parent = fi.FieldType.GetElementType();*
    
  •  				i += 2;*
    
  •  				continue;*
    
  •  			}*
    
  •  			if (fi.FieldType.IsGenericType)*
    
  •  			{*
    
  •  				parent = fi.FieldType.GetGenericArguments()[0];*
    
  •  				i += 2;*
    
  •  				continue;*
    
  •  			}*
    
  •  			parent = fi.FieldType;*
    
  •  		}*
    
  •  		else*
    
  •  		{*
    
  •  			break;*
    
  •  		}*
    
  •  	}*
    
  •  	if (fi == null)*
    
  •  	{*
    
  •  		if (type.BaseType != null)*
    
  •  		{*
    
  •  			return GetFieldViaPath(type.BaseType, path);*
    
  •  		}*
    
  •  		else*
    
  •  		{*
    
  •  			return null;*
    
  •  		}*
    
  •  	}*
    
  •  	return fi;*
    
  •  }*