Get the instance the SerializedProperty belongs to in a CustomPropertyDrawer

Here’s what my PropertyDrawer class looks like.

[CustomPropertyDrawer (typeof(OnChangeAttribute))]
public class OnChangePropertyDrawer : PropertyDrawer
{
  public override void OnGUI (Rect position, SerializedProperty prop, GUIContent label)
  {
    EditorGUI.BeginChangeCheck ();
    string val = EditorGUI.TextField (position, label, prop.stringValue);
    if (EditorGUI.EndChangeCheck ()) {
      // ... get instance of object that I'm changing (an instance of ObjectB)
      // ... call a method on that instance

      prop.stringValue = val;
    }
  }
}

When there’s a change to a property in the GUI, I’d like to be able to get the instance of the object that has changed.

Is there any way to get the reference to that instance?

EDIT:

The problem seems to be that the property my PropertyDrawer is drawing is part of an array of objects in another class.

public class ObjectA : MonoBehaviour {
  public ObjectB[] objects;
}

[System.Serializable]
public class ObjectB {
  [OnChange ("StringValue")] public int myProperty
}

So when I change myProperty in the inspector, the value of prop.serializedObject.targetObject in the PropertyDrawer class is an instance of type ObjectA. I want the instance of ObjectB that I’ve changed.

EDIT:

ObjectA is a MonoBehaviour class that I’ve added as a component to a GameObject. ObjectB is a generic, serializable class I defined, the instances of which are created in the inspector. I set the size of the array, and enter the properties of each instance shown in the inspector.

Here you go - this will use reflection to descend to the parent of the object you are looking with a SerializedProperty

using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Linq;
using System;
using System.Reflection;

...

public object GetParent(SerializedProperty prop)
{
	var path = prop.propertyPath.Replace(".Array.data[", "[");
	object obj = prop.serializedObject.targetObject;
	var elements = path.Split('.');
	foreach(var element in elements.Take(elements.Length-1))
	{
		if(element.Contains("["))
		{
			var elementName = element.Substring(0, element.IndexOf("["));
			var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[","").Replace("]",""));
			obj = GetValue(obj, elementName, index);
		}
		else
		{
			obj = GetValue(obj, element);
		}
	}
	return obj;
}

public object GetValue(object source, string name)
{
	if(source == null)
		return null;
	var type = source.GetType();
	var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
	if(f == null)
	{
		var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
		if(p == null)
			return null;
		return p.GetValue(source, null);
	}
	return f.GetValue(source);
}

public object GetValue(object source, string name, int index)
{
	var enumerable = GetValue(source, name) as IEnumerable;
	var enm = enumerable.GetEnumerator();
	while(index-- >= 0)
		enm.MoveNext();
	return enm.Current;
}

Here’s a test project showing how it works by just inspecting all of the properties of a particular type and checking their parents: [9383-serializedproperty.unitypackage.zip|9383]

If your class is a SerializedObject (or any child of Object), you can use property.objectReferenceValue to get a reference to it.

GameObject ob = property.objectReferenceValue as GameObject;

As vexe & codingChris point out, you can use PropertyDrawer.fieldInfo for all Serializable types:

GameObject ob = fieldInfo.GetValue(property.serializedObject.targetObject) as GameObject;
ObjectB ob = fieldInfo.GetValue(property.serializedObject.targetObject) as ObjectB;

All of these methods work even if your object is inside an array.


Demonstration of using this code:

[Demo]
public GameObject thing;

[CustomPropertyDrawer(typeof(DemoAttribute))]
public class DemoDrawer : PropertyDrawer {
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        EditorGUI.BeginProperty(position, label, property);
        {
            Debug.Log(string.Format("objectReferenceValue={0} fieldInfo={1}", property.objectReferenceValue, fieldInfo.GetValue(property.serializedObject.targetObject) as GameObject));
            EditorGUI.PropertyField(position, property);
        }
        EditorGUI.EndProperty();
    }
}

Just to improve upon @whydoidoit’s answer. If you want to find derived fields and properties when fetching the value, you can do the following.

public static object GetValue(object source, string name)
    {
        if (source == null)
            return null;

        var type = source.GetType();

        var f = FindFieldInTypeHierarchy(type, name);

        if (f == null)
        {
            var p = FindPropertyInTypeHierarchy(type, name);
            if (p == null)
                return null;
            return p.GetValue(source, null);
        }
        return f.GetValue(source);
    }

    public static FieldInfo FindFieldInTypeHierarchy(Type providedType, string fieldName)
    {
        FieldInfo field = providedType.GetField(fieldName, (BindingFlags)(-1));
        

        while (field == null && providedType.BaseType != null)
        {
            providedType = providedType.BaseType;
            field = providedType.GetField(fieldName, (BindingFlags)(-1));
        }

        return field;
    }

    public static PropertyInfo FindPropertyInTypeHierarchy(Type providedType, string propertyName)
    {
        PropertyInfo property = providedType.GetProperty(propertyName, (BindingFlags)(-1));


        while (property == null && providedType.BaseType != null)
        {
            providedType = providedType.BaseType;
            property = providedType.GetProperty(propertyName, (BindingFlags)(-1));
        }

        return property;
    }