- Home /
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?^^
Answer by Glurth · Apr 30, 2017 at 05:55 PM
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 (https://msdn.microsoft.com/en-us/library/system.reflection.fieldinfo.fieldtype(v=vs.110).aspx)
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;
}
ParentType
is a poor name for that variable.. probably should have called it containingObjectType
or something like that... NO inheritance stuff is actually involved here.
Your GetType function would need to be called something else - the compiler calls instance methods before it calls extension methods and so doing property.GetType()
will call the GetType
method from System.Object
.
Removed "this" from the answer's GetType fnction params. Good Catch.
Thanks mate. I've just seen the response, am looking into it now ;-)
Jesus, and I just went nuts with the property.type string^^ Like looking into every namespace and nested class there is^^ Thanks mate, Will do some refactoring... sometime^^
Answer by cp- · Jul 21, 2021 at 03:44 PM
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.
Thank you! This is all you need for custom property drawers.
Just to clarify for others: If you ACTUALLY HAVE a reference to an instantiated PropertyDrawer that handles the type of the value stored in the SerializedProperty, or if you are DEFINING a custom property drawer to draw it, this is the correct answer. If you DON'T have one, like in the Q, you'll need to use another one of the methods presented in other answers.
@Glurth is right of course: This only works for within PropertyDrawers. I may have misread the original question, my bad!
All good- this is complex stuff! So, this answer is actually a really good one to have on the list here, particularly since people that ARE working on CustomPropertyDrawers are actually finding this page on google!
Answer by bremyBBW · Dec 18, 2017 at 04:22 PM
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;
Answer by Castle24 · Feb 10, 2020 at 03:45 PM
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[i]);
// 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;
}
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.
Thank you ! Your update fall right in time as I came Across the same issue as you just now !
Answer by el_trex · Mar 07, 2020 at 11:17 PM
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[i], flags);
// 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;
}
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.
Your answer
Follow this Question
Related Questions
Change gameview ratio / resolution using a custom inspector? 1 Answer
Deleting GameObjects from ReorderableList with proper undo 0 Answers
Learning Editor Scripting 1 Answer
How to Hide/Show List or Array in the inspector based on a variable? 0 Answers
How to correctly register Undos in Custom Inspector (within OnInspectorGUI)? 4 Answers