A smarter Way to get the Type of SerializedProperty
Asked Answered
E

7

1

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?^^

Engstrom answered 21/11, 2023 at 3:53 Comment(1)

yes, otherwise the index array for which vertex goes into which triangle would be messed up.

Fevre
B
0

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;
    }
Barrack answered 6/6, 2023 at 2:41 Comment(5)

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.

Barrack

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.

Humiliation

Thanks mate. I've just seen the response, am looking into it now ;-)

Engstrom

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^^

Engstrom

Excuse me I have another problem. I downloaded a model with animation (like a walking man). I tried to access those vertices coordinate using MeshFilter when the man walks, but however I choose the frame to run the script, the vertices I got is the initial vertices (Which shows the man just standing over there, never walked). So do you know how can I access vertices coordinate of a model in an animation? Thx

Edessa
M
0

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;

Morehead answered 6/6, 2023 at 2:26 Comment(1)

Mesh.vertices doesn't change at each frame. For the walking man animation you're talking about, it's probably using a SkinnedMeshRenderer, where the mesh is deformed with bones. However, you can get the mesh after deformation by using the bakeMesh function : https://docs.unity3d.com/ScriptReference/SkinnedMeshRenderer.BakeMesh.html By baking two consecutive frames, you can compare the vertices positions. The indices will not change between frames.

Contrite
U
0

It’s based on @Barrack 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.
----------

Upturned answered 6/6, 2023 at 2:26 Comment(2)

Thank you ! Your update fall right in time as I came Across the same issue as you just now !

Shikoku

Yes, bakeMesh make a snapshot. it works, thank you very much!

Edessa
S
0

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 @Morehead and the last iteration of @Upturned to fix the error I got with a private, but serialized, field of mine.

Shikoku answered 6/6, 2023 at 2:27 Comment(0)
U
0

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;*
    
  •  }*
    
Unship answered 23/12, 2020 at 11:42 Comment(0)
M
0

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.

Midi answered 7/11, 2023 at 22:50 Comment(3)

Thank you! This is all you need for custom property drawers.

Flabellate

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.

Barrack

@Barrack is right of course: This only works for within PropertyDrawers. I may have misread the original question, my bad!

Midi
L
0

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.

Lalonde answered 22/1 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.