Search Unity

Get a general object value from SerializedProperty?

Discussion in 'Scripting' started by VipHaLongPro, May 20, 2015.

  1. VipHaLongPro

    VipHaLongPro

    Joined:
    Mar 8, 2015
    Posts:
    49
    I have a requirement to get an object value (some kind of boxing or uncast value) from a SerializedProperty. This kind of property supports only properties like: boolValue, stringValue, colorValue, .... but I have a method requiring just value of this SerializedProperty without knowing what its type is beforehand (at the coding time). With these easy-to-use properties, I have to check the value type of SerializedProperty (using some reflection) first before choosing to return which (boolValue, string Value, colorValue, ...). But even with that the returned type is still limited.

    I wonder if there is any way to get the value of SerializedProperty as a simple object like I can do using Reflection when knowing the property name and an instance of the object. In this case I know only about the property name (SerializedProperty.name) but there is no way to get to the object instance. The property serializedObject returns the root serialized MonoBehaviour, not the instance of its one complex property, e.g:

    Code (CSharp):
    1. public class RootComponent : MonoBehaviour {
    2.       public ComplexProperty complexProp;
    3. }
    4.  
    5. public class ComplexProperty {
    6.       //....
    7. }
    To get the value using Reflection, I must somehow get access to the instance of ComplexProperty (from its sub SerializedProperty in PropertyDrawer's OnGUI) in this case, but serializedObject.targetObject returns the instance of RootComponent. The SerializedProperty whose value I want to get is in fact one of sub-properties of ComplexProperty. I implemented a PropertyDrawer for ComplexProperty and now I want to improve it.

    I hope someone could point me in the right direction with this issue. I don't really hope much because looks like there is not much about SerializedProperty here. Thank you all for your help.
     
    Last edited: May 20, 2015
    thepersonguy123 and vxdcxw like this.
  2. pplasto

    pplasto

    Joined:
    Aug 28, 2012
    Posts:
    2
    Code (CSharp):
    1. var targetObject = property.serializedObject.targetObject;
    2.             var targetObjectClassType = targetObject.GetType();
    3.             var field = targetObjectClassType.GetField(property.propertyPath);
    4.             if (field != null)
    5.             {
    6.                 var value = field.GetValue(targetObject);
    7.                 Debug.Log(value.s);
    8.             }
     
    cdiggins, Hecocos, awsapps and 2 others like this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,538
  4. pplasto

    pplasto

    Joined:
    Aug 28, 2012
    Posts:
    2
    Awesome...

    but also has field is null problem?
     
    Last edited: Sep 24, 2015
  5. dCalle

    dCalle

    Joined:
    Dec 16, 2013
    Posts:
    55
    wow, and I stumble over that now...
    thanks^^

    (my stuff now looks like crap compared to yours.... geez^^)

    I'm freaking baffled actually... youd did that back in 2015? damn, i feel f-ing stupid now... can we be, like friends^^ hahaha

     
    Last edited: Nov 11, 2017
    ModLunar likes this.
  6. SirIntruder

    SirIntruder

    Joined:
    Aug 16, 2013
    Posts:
    49
    How hard would it be for Unity to just expose "value" property in SerializedProperty which would return raw System.Object reference (as there are colorValue, floatValue, vector3Value, etc.)? Every time I want to make a property drawer for some serialized class/struct it is this stupid problem again.

    I wrote something similar to lordofduct's solution recently (parsing string path) but maintaing this code and having to do all that reflection/string stuff on every gui draw (it can slow down Unity when drawing tables/lists) just seems like a huge overkill, I don't see a reason why SerializedProperty should have that feature out of the box

    https://docs.unity3d.com/550/Documentation/ScriptReference/SerializedProperty.html
     
  7. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Dude, I totally second this. -- What a major PITA to do this all the time. D:
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,775
    I know I'm necro-ing this a bit, but I can't believe this is still impossible without us deserializing the damned thing ourselves.

    I can't just make a PropertyField on this because it's a get/set property, which is ANOTHER baffling limitation of SerializedProperty. (I understand that it can't safely do this for all C# properties because getters/setters can trigger other things to happen, but for the love of criminy LET ME DO IT IF I PINKY PROMISE THAT IT'S SAFE.) I can't just use the private field either because the entire reason I made it a property is to perform the validation checks when it gets set.

    How can I possibly have the class validate itself? Not only can I not use the property directly, but I can't even get a reference to the object to manually call some validation function afterwards.
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,538
    Option 8,734 that Unity lacks and why I maintain an entire library of tools that bend Unity to my needs.
     
  10. vedram

    vedram

    Joined:
    May 23, 2018
    Posts:
    5
    Sorry for necroing, but I wrote simple extension methods for SerializedProperty class - GetValue() and SetValue(), which work even with nested values (e.g. value inside struct inside class), and wanted to share them with anybody finding this thread over Google in search how to get/set values of SerializedProperty.

    Usage:
    Code (CSharp):
    1. MyCustomClass val = (MyCustomClass)property.GetValue();
    2. MyCustomClass val2 = new MyCustomClass();
    3. property.SetValue( val2 );
    Currently it doesn't work with arrays, but it shouldn't be problem to extend it, just I didn't need that feature so far.

    And code:
    Code (CSharp):
    1. public static class MyExtensions
    2. {
    3. #if UNITY_EDITOR
    4.     // Gets value from SerializedProperty - even if value is nested
    5.     public static object GetValue( this UnityEditor.SerializedProperty property )
    6.     {
    7.         object obj = property.serializedObject.targetObject;
    8.  
    9.         FieldInfo field = null;
    10.         foreach( var path in property.propertyPath.Split( '.' ) )
    11.         {
    12.             var type = obj.GetType();
    13.             field = type.GetField( path );
    14.             obj = field.GetValue( obj );
    15.         }
    16.         return obj;
    17.     }
    18.  
    19.     // Sets value from SerializedProperty - even if value is nested
    20.     public static void SetValue( this UnityEditor.SerializedProperty property, object val )
    21.     {
    22.         object obj = property.serializedObject.targetObject;
    23.  
    24.         List<KeyValuePair<FieldInfo, object>> list = new List<KeyValuePair<FieldInfo, object>>();
    25.  
    26.         FieldInfo field = null;
    27.         foreach( var path in property.propertyPath.Split( '.' ) )
    28.         {
    29.             var type = obj.GetType();
    30.             field = type.GetField( path );
    31.             list.Add( new KeyValuePair<FieldInfo, object>( field, obj ) );
    32.             obj = field.GetValue( obj );
    33.         }
    34.  
    35.         // Now set values of all objects, from child to parent
    36.         for( int i = list.Count - 1; i >= 0; --i )
    37.         {
    38.             list[i].Key.SetValue( list[i].Value, val );
    39.             // New 'val' object will be parent of current 'val' object
    40.             val = list[i].Value;
    41.         }
    42.     }
    43. #endif // UNITY_EDITOR
    44. }
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,538
    @vedram - I will point out that your implementation does not account for the way 'SerializedObject.propertyPath' returns values whose path are in an array/list.

    If you check out the code I linked to before (EditorHelper) the methods GetTargetObjectOfProperty and SetTargetObjectOfProperty are effectively the same thing as your 2 methods here, but accounting for array/lists.
     
  12. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Sorry for necroing this ancient thread yet again, but I found something rather depressing:

    Unity actually does have an implementation of this (almost). The UnityEditor.ScriptAttributeUtility class has a GetFieldInfoFromPropertyPath method which does the same thing we're all doing - splits the given property path by "." and goes through each part to find the field info and handle arrays.

    There are two sad parts to that:
    • It's an internal class so we can't access it without reflection.
    • It doesn't return the target object we want, only the final FieldInfo so we would need to re-implement the entire thing anyway.
    So they've done like 90% of the work we need but won't allow us to make use of it ...

    Also, if anyone's interested in my implementation, it's available in my UltEvents plugin (link in my signature) in the SerializedPropertyAccessor class. It handles nesting and arrays and is optimised for repeated use in GUI methods.
     
  13. hearstzhang

    hearstzhang

    Joined:
    Apr 1, 2019
    Posts:
    17
  14. net8floz

    net8floz

    Joined:
    Oct 21, 2017
    Posts:
    37
    Code (CSharp):
    1.         private static MethodInfo internal_GetFieldInfoAndStaticTypeFromProperty;
    2.  
    3.         public static FieldInfo GetFieldInfoAndStaticType(this SerializedProperty prop, out Type type) {
    4.             if (internal_GetFieldInfoAndStaticTypeFromProperty == null) {
    5.                 foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
    6.                     foreach (var t in assembly.GetTypes()) {
    7.                         if (t.Name == "ScriptAttributeUtility") {
    8.                             internal_GetFieldInfoAndStaticTypeFromProperty = t.GetMethod("GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    9.                             break;
    10.                         }
    11.                     }
    12.                     if (internal_GetFieldInfoAndStaticTypeFromProperty != null) break;
    13.                 }
    14.             }
    15.             var p = new object[] { prop, null };
    16.             var fieldInfo = internal_GetFieldInfoAndStaticTypeFromProperty.Invoke(null, p) as FieldInfo;
    17.             type = p[1] as Type;
    18.             return fieldInfo;
    19.         }
    20.  
    21.         public static T GetCustomAttributeFromProperty<T>(this SerializedProperty prop) where T : System.Attribute {
    22.             var info = prop.GetFieldInfoAndStaticType(out _);
    23.             return info.GetCustomAttribute<T>();
    24.         }
    I just want to share this somewhere so other poor souls will find it on google. This is for anyone who is sick of dealing with this stuff, just using reflection to use unity's internal stuff. I need this because introducing managed references into the serialization system along with arrays is just not worth the time to think about. IMO this stuff should be exposed. Also idk the entire reflection api so feel free to suggest better ways to resolve the function.
     
    Aldeminor and tratteo like this.
  15. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,008
    Note if you're doing such reflection hacks you should always aim for creating a delegate out of the method you're interested in. This of course only works if the involved types are publicly available. However since Type, FieldInfo and SerializedProperty are all public types we can do this:

    Code (CSharp):
    1.  
    2. using UnityEditor;
    3. using System;
    4. using System.Reflection;
    5.  
    6. public static class SerializedProperty_Extention
    7. {
    8.     private delegate FieldInfo GetFieldInfoAndStaticTypeFromProperty(SerializedProperty aProperty, out Type aType);
    9.     private static GetFieldInfoAndStaticTypeFromProperty m_GetFieldInfoAndStaticTypeFromProperty;
    10.  
    11.     public static FieldInfo GetFieldInfoAndStaticType(this SerializedProperty prop, out Type type)
    12.     {
    13.         if (m_GetFieldInfoAndStaticTypeFromProperty == null)
    14.         {
    15.             foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
    16.             {
    17.                 foreach (var t in assembly.GetTypes())
    18.                 {
    19.                     if (t.Name == "ScriptAttributeUtility")
    20.                     {
    21.                         MethodInfo mi = t.GetMethod("GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
    22.                         m_GetFieldInfoAndStaticTypeFromProperty = (GetFieldInfoAndStaticTypeFromProperty)Delegate.CreateDelegate(typeof(GetFieldInfoAndStaticTypeFromProperty), mi);
    23.                         break;
    24.                     }
    25.                 }
    26.                 if (m_GetFieldInfoAndStaticTypeFromProperty != null) break;
    27.             }
    28.             if (m_GetFieldInfoAndStaticTypeFromProperty == null)
    29.             {
    30.                 UnityEngine.Debug.LogError("GetFieldInfoAndStaticType::Reflection failed!");
    31.                 type = null;
    32.                 return null;
    33.             }
    34.         }
    35.         return m_GetFieldInfoAndStaticTypeFromProperty(prop, out type);
    36.     }
    37.  
    38.     public static T GetCustomAttributeFromProperty<T>(this SerializedProperty prop) where T : System.Attribute
    39.     {
    40.         var info = prop.GetFieldInfoAndStaticType(out _);
    41.         return info.GetCustomAttribute<T>();
    42.     }
    43. }
    44.  
    The great advantage is that using the delegate has no overhead (no temporary argument array needed). This way you can use the normal C# dispatch and almost get the same performance as if you had direct access to the method.
     
  16. lubba64_unity

    lubba64_unity

    Joined:
    Sep 16, 2020
    Posts:
    9
    honestly unity not having a default value I can reference is nuts, thanks for the hacky reflection workaround. hopefully they take notice and fix this in the 2020 betas.
     
    ModLunar, net8floz and awesomedata like this.
  17. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Why not mention it in the Workflow Case Study thread? -- Show them your pain!
     
    NotaNaN likes this.
  18. net8floz

    net8floz

    Joined:
    Oct 21, 2017
    Posts:
    37
    Thanks for the tip! I was hoping I'd get feedback on the code
     
  19. net8floz

    net8floz

    Joined:
    Oct 21, 2017
    Posts:
    37
    Ping me if you need any other serialized object hacks I've been in that arena the past few days.
     
  20. lubba64_unity

    lubba64_unity

    Joined:
    Sep 16, 2020
    Posts:
    9
  21. Bodix_

    Bodix_

    Joined:
    May 6, 2018
    Posts:
    15
    If you use SerializedProperty in PropertyDrawer you can use the following extension method to get object value from SerializedProperty:

    Code (CSharp):
    1.  
    2. public static T GetSerializedValue<T>(this PropertyDrawer propertyDrawer, SerializedProperty property)
    3. {
    4.     object @object = propertyDrawer.fieldInfo.GetValue(property.serializedObject.targetObject);
    5.  
    6.     // UnityEditor.PropertyDrawer.fieldInfo returns FieldInfo:
    7.     // - about the array, if the serialized object of property is inside the array or list;
    8.     // - about the object itself, if the object is not inside the array or list;
    9.  
    10.     // We need to handle both situations.
    11.     if (@object.GetType().GetInterfaces().Contains(typeof(IList<T>)))
    12.     {
    13.         int propertyIndex = int.Parse(property.propertyPath[property.propertyPath.Length - 2].ToString());
    14.  
    15.         return ((IList<T>) @object)[propertyIndex];
    16.     }
    17.     else
    18.     {
    19.         return (T) @object;
    20.     }
    21. }
    22.  
    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(YourSerializedType))]
    2. public class ComponentResetterDrawer : PropertyDrawer
    3. {
    4.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    5.     {
    6.         YourSerializedType serializedValue = this.GetSerializedValue<YourSerializedType>(property);
    7.     }
    8. }
    UPD

    Note:
    Not working with nested types.
     
    Last edited: Dec 8, 2020
  22. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Sure ... if you don't want to support nested fields or array indices above 9 or lists.

    Otherwise you'll need the more reliable methods that have already been posted in this thread.
     
    Bunny83 likes this.
  23. Bodix_

    Bodix_

    Joined:
    May 6, 2018
    Posts:
    15
    Lists work with this method. Nested types not tested.

    UPD

    Nested types not working.
     
    Last edited: Dec 8, 2020
  24. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You're casting it to an array. That will cause an exception if the field is a list.
     
  25. Bodix_

    Bodix_

    Joined:
    May 6, 2018
    Posts:
    15
    I thought exactly the same. Tested with a list and it worked. Most likely, Unity's serialization system casts lists to the array under the hood.
     
  26. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    You can't cast a list to an array. If the field is a list, fieldInfo.GetValue will return a list and your cast will throw an exception. Unity's serialization system doesn't come into play there.

    What code did you test it with?
     
  27. Bodix_

    Bodix_

    Joined:
    May 6, 2018
    Posts:
    15
    Oh, you mean if the serializable object itself is a list. I'll check now.

    UPD

    Everything is ok with the list as a serializable object. It doesn't make sense because of fieldInfo.GetValue in array case will return an array of lists. Or am I still not understanding you correctly?
     
    Last edited: Dec 8, 2020
  28. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    Make a
    [SerializeField] List<float> _MyList;
    and try using your method to get the value of the first element in the list (not the list itself, the first float).
     
  29. Bodix_

    Bodix_

    Joined:
    May 6, 2018
    Posts:
    15
    This version of the extension method for
    SerializedProperty
    works also for arrays, lists and nested types:
    Code (CSharp):
    1.  
    2. public static class SerializedPropertyExtensions
    3. {
    4.     public static T GetSerializedValue<T>(this SerializedProperty property)
    5.     {
    6.         object @object = property.serializedObject.targetObject;
    7.         string[] propertyNames = property.propertyPath.Split('.');
    8.  
    9.         // Clear the property path from "Array" and "data[i]".
    10.         if (propertyNames.Length >= 3 && propertyNames[propertyNames.Length - 2] == "Array")
    11.             propertyNames = propertyNames.Take(propertyNames.Length - 2).ToArray();
    12.  
    13.         // Get the last object of the property path.
    14.         foreach (string path in propertyNames)
    15.         {
    16.             @object = @object.GetType()
    17.                 .GetField(path, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
    18.                 .GetValue(@object);
    19.         }
    20.  
    21.         if (@object.GetType().GetInterfaces().Contains(typeof(IList<T>)))
    22.         {
    23.             int propertyIndex = int.Parse(property.propertyPath[property.propertyPath.Length - 2].ToString());
    24.  
    25.             return ((IList<T>) @object)[propertyIndex];
    26.         }
    27.         else return (T) @object;
    28.     }
    29. }
    30.  
     
    Last edited: Dec 8, 2020
    kmedved and awsapps like this.
  30. Yany

    Yany

    Joined:
    May 24, 2013
    Posts:
    96
    Unfortunately this does not work for Property drawers when the property is on an object that is in a list of properties of a monobehaviour. (so when basically the array of data[] is not at the end of the property path but somewhere in the middle.
     
    vstefaniuk and awsapps like this.
  31. Bodix_

    Bodix_

    Joined:
    May 6, 2018
    Posts:
    15
    Could you please show an example?
     
  32. Yany

    Yany

    Joined:
    May 24, 2013
    Posts:
    96
    When the array is not at the bottom of the property tree, but somewhere in the middle, the code won't work. Unfortunately I changed the complete code here (using SerializeReference made me a lot of headache and also simplified enormous amount of unnecessary code the same time) thus I don't have the exact structure to reproduce the situation.
     
  33. Anisoropos

    Anisoropos

    Joined:
    Jul 30, 2012
    Posts:
    102
    Given there's only a fixed number of types and I wanted to avoid string operations for potential performance issues I opted for the following ::

    It's missing a couple of cases so if anyone knows how to get those values properly that'd be nice.

    The fact that it returns an object may be limiting for some applications but it works well for callbacks (ie. OnValueChangedAttribute) with MethodInfo.Invoke()

    Code (CSharp):
    1.  
    2.         private static object GetValue(UnityEditor.SerializedProperty property)
    3.         {
    4.             switch (property.propertyType)
    5.             {
    6.                 case UnityEditor.SerializedPropertyType.Generic:
    7.                     return null; // TODO
    8.                 case UnityEditor.SerializedPropertyType.Integer:
    9.                     return property.intValue;
    10.                 case UnityEditor.SerializedPropertyType.Boolean:
    11.                     return property.boolValue;
    12.                 case UnityEditor.SerializedPropertyType.Float:
    13.                     return property.floatValue;
    14.                 case UnityEditor.SerializedPropertyType.String:
    15.                     return property.stringValue;
    16.                 case UnityEditor.SerializedPropertyType.Color:
    17.                     return property.colorValue;
    18.                 case UnityEditor.SerializedPropertyType.ObjectReference:
    19.                     return property.objectReferenceValue;
    20.                 case UnityEditor.SerializedPropertyType.LayerMask:
    21.                     return null;  // TODO
    22.                 case UnityEditor.SerializedPropertyType.Enum:
    23.                     return property.enumValueIndex;
    24.                 case UnityEditor.SerializedPropertyType.Vector2:
    25.                     return property.vector2Value;
    26.                 case UnityEditor.SerializedPropertyType.Vector3:
    27.                     return property.vector3Value;
    28.                 case UnityEditor.SerializedPropertyType.Vector4:
    29.                     return property.vector4Value;
    30.                 case UnityEditor.SerializedPropertyType.Rect:
    31.                     return property.rectValue;
    32.                 case UnityEditor.SerializedPropertyType.ArraySize:
    33.                     return property.arraySize;
    34.                 case UnityEditor.SerializedPropertyType.Character:
    35.                     return property.stringValue; // MAYBE?
    36.                 case UnityEditor.SerializedPropertyType.AnimationCurve:
    37.                     return property.animationCurveValue;
    38.                 case UnityEditor.SerializedPropertyType.Bounds:
    39.                     return property.boundsValue;
    40.                 case UnityEditor.SerializedPropertyType.Gradient:
    41.                     return null; // TOOD
    42.                 case UnityEditor.SerializedPropertyType.Quaternion:
    43.                     return property.quaternionValue;
    44.                 case UnityEditor.SerializedPropertyType.ExposedReference:
    45.                     return property.exposedReferenceValue;
    46.                 case UnityEditor.SerializedPropertyType.FixedBufferSize:
    47.                     return property.fixedBufferSize;
    48.                 case UnityEditor.SerializedPropertyType.Vector2Int:
    49.                     return property.vector2IntValue;
    50.                 case UnityEditor.SerializedPropertyType.Vector3Int:
    51.                     return property.vector3IntValue;
    52.                 case UnityEditor.SerializedPropertyType.RectInt:
    53.                     return property.rectIntValue;
    54.                 case UnityEditor.SerializedPropertyType.BoundsInt:
    55.                     return property.boundsIntValue;
    56.                 case UnityEditor.SerializedPropertyType.ManagedReference:
    57.                     return null;// property.managedReferenceValue; // no get
    58.             }
    59.  
    60.             return null;
    61.         }
     
    Celezt and Eldoir like this.
  34. swedishfisk

    swedishfisk

    Joined:
    Oct 14, 2016
    Posts:
    57
    Could you expand a bit on this? I just found this thread since I suspected that working with SerializeReference could help with the topic but I haven't played around with it yet. Any pointers?
     
  35. LuisMer

    LuisMer

    Joined:
    Jan 22, 2019
    Posts:
    3
    @Bodix_ solution works like a charm. I have been heavily tormented with a problem of this type for several hours, concretely with the nested class type. Thank you so much
     
  36. awsapps

    awsapps

    Joined:
    Jun 15, 2021
    Posts:
    74
    I think I understood @Yiani comment, here is a version with nested arrays:

    Code (CSharp):
    1.    
    2. using System.Collections.Generic;
    3. using System.Collections;
    4. using System.Linq;
    5. using System;
    6.  
    7. public static class SerializedPropertyExtensions
    8.     {
    9.         public static T GetSerializedValue<T>(this SerializedProperty property)
    10.         {
    11.             object @object = property.serializedObject.targetObject;
    12.             string[] propertyNames = property.propertyPath.Split('.');
    13.  
    14.             List<string> propertyNamesClean = new List<String>();
    15.  
    16.             for (int i = 0; i < propertyNames.Count(); i++)
    17.             {
    18.                 if (propertyNames[i] == "Array")
    19.                 {
    20.                     if (i != (propertyNames.Count() -1) && propertyNames[i + 1].StartsWith("data"))
    21.                     {
    22.                         int pos = int.Parse(propertyNames[i + 1].Split('[', ']')[1]);
    23.                         propertyNamesClean.Add($"-GetArray_{pos}");
    24.                         i++;
    25.                     }
    26.                     else
    27.                         propertyNamesClean.Add(propertyNames[i]);
    28.                 }
    29.                 else
    30.                     propertyNamesClean.Add(propertyNames[i]);
    31.             }
    32.             // Get the last object of the property path.
    33.             foreach (string path in propertyNamesClean)
    34.             {
    35.                 if (path.StartsWith("-GetArray"))
    36.                 {
    37.                     string[] split = path.Split('_');
    38.                     int index = int.Parse(split[split.Count() - 1]);
    39.                     IList l = (IList)@object;
    40.                     @object = l[index];
    41.                 }
    42.                 else
    43.                 {
    44.                     @object = @object.GetType()
    45.                         .GetField(path, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
    46.                         .GetValue(@object);
    47.                 }
    48.             }
    49.  
    50.             return (T)@object;
    51.         }
    52.     }
     
  37. abegue

    abegue

    Joined:
    Jan 28, 2019
    Posts:
    24
    Here is a proposition of a solution, managing nested, arrays and fields in bases classes (like if you want to access a field that is in class A but your target object is class B : A).

    Code (CSharp):
    1. public static class SerializedPropertyExtentions
    2.     {
    3.         private static Regex ArrayIndexCapturePattern = new Regex(@"\[(\d*)\]");
    4.  
    5.         public static T GetTarget<T>(this SerializedProperty prop)
    6.         {
    7.             string[] propertyNames = prop.propertyPath.Split('.');
    8.             object target = prop.serializedObject.targetObject;
    9.             bool isNextPropertyArrayIndex = false;
    10.             for (int i = 0; i < propertyNames.Length && target != null; ++i)
    11.             {
    12.                 string propName = propertyNames[i];
    13.                 if (propName == "Array")
    14.                 {
    15.                     isNextPropertyArrayIndex = true;
    16.                 }
    17.                 else if (isNextPropertyArrayIndex)
    18.                 {
    19.                     isNextPropertyArrayIndex = false;
    20.                     int arrayIndex = ParseArrayIndex(propName);
    21.                     object[] targetAsArray = (object[])target;
    22.                     target = targetAsArray[arrayIndex];
    23.                 }
    24.                 else
    25.                 {
    26.                     target = GetField(target, propName);
    27.                 }
    28.             }
    29.             return (T)target;
    30.         }
    31.  
    32.         private static object GetField(object target, string name, Type targetType = null)
    33.         {
    34.             if (targetType == null)
    35.             {
    36.                 targetType = target.GetType();
    37.             }
    38.  
    39.             FieldInfo fi = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    40.             if (fi != null)
    41.             {
    42.                 return fi.GetValue(target);
    43.             }
    44.  
    45.             // If not found, search in parent
    46.             if (targetType.BaseType != null)
    47.             {
    48.                 return GetField(target, name, targetType.BaseType);
    49.             }
    50.             return null;
    51.         }
    52.  
    53.         private static int ParseArrayIndex(string propName)
    54.         {
    55.             Match match = ArrayIndexCapturePattern.Match(propName);
    56.             if (!match.Success)
    57.             {
    58.                 throw new Exception($"Invalid array index parsing in {propName}");
    59.             }
    60.  
    61.             return int.Parse(match.Groups[1].Value);
    62.         }
    63.     }
    Using:
    property.GetTarget<MyClass>();


    Hope it will help!
     
    Nexer8 likes this.
  38. luisfinke

    luisfinke

    Joined:
    Dec 2, 2017
    Posts:
    8
    @lordofduct I was trying to pull in your function, and each thing that I pulled in basically required me to pull in something else until I'm pulling in basically the entire library. Point being, is there any way I can use your methods `GetTargetObjectOfProperty` and `SetTargetObjectOfProperty` without having to pull in your entire github repository? It just seems like a lot to pull in just to set one value on a serialized property
     
  39. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,538
    The only dependency GetTargetObjectOfProperty has is to the 2 overloads GetValue_Imp:
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4. using System.Reflection;
    5.  
    6. public static class EditorHelper
    7. {
    8.     /// <summary>
    9.     /// Gets the object the property represents.
    10.     /// </summary>
    11.     /// <param name="prop"></param>
    12.     /// <returns></returns>
    13.     public static object GetTargetObjectOfProperty(SerializedProperty prop)
    14.     {
    15.         var path = prop.propertyPath.Replace(".Array.data[", "[");
    16.         object obj = prop.serializedObject.targetObject;
    17.         var elements = path.Split('.');
    18.         foreach (var element in elements)
    19.         {
    20.             if (element.Contains("["))
    21.             {
    22.                 var elementName = element.Substring(0, element.IndexOf("["));
    23.                 var index = System.Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "").Replace("]", ""));
    24.                 obj = GetValue_Imp(obj, elementName, index);
    25.             }
    26.             else
    27.             {
    28.                 obj = GetValue_Imp(obj, element);
    29.             }
    30.         }
    31.         return obj;
    32.     }
    33.  
    34.     private static object GetValue_Imp(object source, string name)
    35.     {
    36.         if (source == null)
    37.             return null;
    38.         var type = source.GetType();
    39.  
    40.         while (type != null)
    41.         {
    42.             var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    43.             if (f != null)
    44.                 return f.GetValue(source);
    45.  
    46.             var p = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
    47.             if (p != null)
    48.                 return p.GetValue(source, null);
    49.  
    50.             type = type.BaseType;
    51.         }
    52.         return null;
    53.     }
    54.  
    55.     private static object GetValue_Imp(object source, string name, int index)
    56.     {
    57.         var enumerable = GetValue_Imp(source, name) as System.Collections.IEnumerable;
    58.         if (enumerable == null) return null;
    59.         var enm = enumerable.GetEnumerator();
    60.         //while (index-- >= 0)
    61.         //    enm.MoveNext();
    62.         //return enm.Current;
    63.  
    64.         for (int i = 0; i <= index; i++)
    65.         {
    66.             if (!enm.MoveNext()) return null;
    67.         }
    68.         return enm.Current;
    69.     }
    70.  
    71. }
    There are definitely methods in EditorHelper with other dependencies... but if all you need is GetTargetObjectOfProperty, then only take it.
     
  40. Dr-Nick

    Dr-Nick

    Joined:
    Sep 9, 2013
    Posts:
    25
  41. BionicWombatGames

    BionicWombatGames

    Joined:
    Oct 5, 2020
    Posts:
    33
    boxedValue works exactly as expected! Finally!
     
    Wilhelm_LAS, qsleonard and Dr-Nick like this.
  42. swedishfisk

    swedishfisk

    Joined:
    Oct 14, 2016
    Posts:
    57
    Nice to hear that boxedValue is supported in 2022. For those of us still stuck in the past, this script has been tested for all types that can appear in the inspector pre 2022 and should work fine.

    Code (CSharp):
    1. private static object GetValue(SerializedProperty property)
    2. {
    3.     return property.propertyType switch
    4.     {
    5.         SerializedPropertyType.Integer =>
    6.             property.intValue,
    7.         SerializedPropertyType.Boolean =>
    8.             property.boolValue,
    9.         SerializedPropertyType.Float =>
    10.             property.floatValue,
    11.         SerializedPropertyType.String =>
    12.             property.stringValue,
    13.         SerializedPropertyType.Color =>
    14.             property.colorValue,
    15.         SerializedPropertyType.ObjectReference =>
    16.             property.objectReferenceValue,
    17.         SerializedPropertyType.LayerMask =>
    18.             property.intValue,
    19.         SerializedPropertyType.Enum =>
    20.             property.enumValueIndex,
    21.         SerializedPropertyType.Vector2 =>
    22.             property.vector2Value,
    23.         SerializedPropertyType.Vector3 =>
    24.             property.vector3Value,
    25.         SerializedPropertyType.Vector4 =>
    26.             property.vector4Value,
    27.         SerializedPropertyType.Rect =>
    28.             property.rectValue,
    29.         SerializedPropertyType.ArraySize =>
    30.             property.arraySize,
    31.         SerializedPropertyType.Character =>
    32.             property.intValue,
    33.         SerializedPropertyType.AnimationCurve =>
    34.             property.animationCurveValue,
    35.         SerializedPropertyType.Bounds =>
    36.             property.boundsValue,
    37.         SerializedPropertyType.Gradient =>
    38.             GetGradient(property),
    39.         SerializedPropertyType.Quaternion =>
    40.             property.quaternionValue,
    41.         SerializedPropertyType.ExposedReference =>
    42.             property.exposedReferenceValue,
    43.         SerializedPropertyType.FixedBufferSize =>
    44.             property.fixedBufferSize,
    45.         SerializedPropertyType.Vector2Int =>
    46.             property.vector2IntValue,
    47.         SerializedPropertyType.Vector3Int =>
    48.             property.vector3IntValue,
    49.         SerializedPropertyType.RectInt =>
    50.             property.rectIntValue,
    51.         SerializedPropertyType.BoundsInt =>
    52.             property.boundsIntValue,
    53.         SerializedPropertyType.ManagedReference =>
    54.             property.managedReferenceValue,
    55.         SerializedPropertyType.Hash128 =>
    56.             property.hash128Value,
    57.         _ => null
    58.     };
    59. }
    60.  
    61. private static Gradient GetGradient(SerializedProperty gradientProperty)
    62. {
    63.     PropertyInfo propertyInfo = typeof(SerializedProperty).GetProperty("gradientValue",
    64.         BindingFlags.NonPublic | BindingFlags.Instance);
    65.  
    66.     if (propertyInfo == null) return null;
    67.  
    68.     return propertyInfo.GetValue(gradientProperty, null) as Gradient;
    69. }
    70.  
    Note that reflection is needed to get the gradient value since it's marked internal, perhaps an issue should be opened about this?
     
  43. simetra

    simetra

    Joined:
    Aug 31, 2018
    Posts:
    4
    This is not even remotely correct. We need object value for classes that we created not unity's classes (and structs)
     
  44. simetra

    simetra

    Joined:
    Aug 31, 2018
    Posts:
    4
    As it is mentioned (haven't tested myself) it is possible in 2022 versions. Aside from that, all of the answers above (all do the same thing) are wrong. All of them use prop.serializedObject.targetObject , which returns null when your object in not a unity object. What I mean is, you can object value this way if you have class A : MonoB, and the field field is in A. If the field is in class B, and you have A.b of type B field, you can not get value of any serialized property in B, because their target object property refers to a class that is not a UnityObject (B), and thus is null. I have't found a solution for this specific scenario yet.
     
  45. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,008
    I'm not quite sure what you're talking about. The inspector can only inspect objects derived from UnityEngine.Object, by definition. So what do you mean by targetObject is null? "targetObject" has the type UnityEngine.Object, not System.Object. You can only inspect Unity classes. So Meshes, GameObjects, Materials, etc.

    You just claimed that all people here are wrong without clearly stating what is wrong? Again "targetObject" is the root object that is actually serialized as file in the Unity editor. This can be part of a scene or a standalone asset. Those are always UnityEngine.Object derived types. No other types are supported. The only two classes that are usually relevant for extension are MonoBehaviour and ScriptableObject. Maybe you misunderstood some general concept here, though it's hard to tell.

    Just to be clear.
    prop.serializedObject
    accesses the root UnityEngine.Object instance that this property is serialized under. It could be a deeply nested property but it is still part of that root object. "propertyPath" is a string that indicates that relative path inside that root object.
     
    orionsyndrome likes this.
  46. SirIntruder

    SirIntruder

    Joined:
    Aug 16, 2013
    Posts:
    49
    Good news everyone!

    https://docs.unity3d.com/2022.2/Documentation/ScriptReference/SerializedProperty-boxedValue.html
     
    Easiii, SisusCo and Eclextic like this.
  47. Eclextic

    Eclextic

    Joined:
    Sep 28, 2020
    Posts:
    142
  48. SantiagoIsAFK

    SantiagoIsAFK

    Joined:
    Jan 14, 2016
    Posts:
    3
  49. Syganek

    Syganek

    Joined:
    Sep 11, 2013
    Posts:
    85
    I think what @simetra meant was this use case:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class TestScript : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     private SomeProperty _someProperty;
    8.  
    9.     [SerializeField]
    10.     private EmbeddedClass _class;
    11.  
    12. }
    13.  
    14. [Serializable]
    15. public class SomeProperty
    16. {
    17.     [SerializeField]
    18.     private string _value;
    19. }
    20.  
    21. [Serializable]
    22. public class EmbeddedClass
    23. {
    24.     [SerializeField]
    25.     private SomeProperty _someProperty;
    26. }
    This makes the SomeProperty serialized inside a class that is not - directly - inside UnityEngine.Object.

    But in my case (Unity 2021.3) the example drawer below works just fine and it does find the targetObject in both cases. Even if the field is inside a non UnityEngine.Object class directly.

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. [CustomPropertyDrawer(typeof(SomeProperty))]
    5. public class SomePropertyDrawer : PropertyDrawer
    6. {
    7.    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    8.    {
    9.       return property.serializedObject.targetObject == null ? EditorGUIUtility.singleLineHeight : EditorGUIUtility.singleLineHeight * 2;
    10.    }
    11.  
    12.    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    13.    {
    14.       if (property.serializedObject.targetObject == null)
    15.       {
    16.          EditorGUI.LabelField(position, new GUIContent("This property does not belong to a UnityEngine.Object"));
    17.       }
    18.       else
    19.       {
    20.          var valueProperty = property.FindPropertyRelative("_value");
    21.  
    22.          var firstRect = new Rect(position);
    23.          firstRect.height = EditorGUIUtility.singleLineHeight;
    24.  
    25.          var secondRect = new Rect(firstRect);
    26.          secondRect.y += EditorGUIUtility.singleLineHeight;
    27.      
    28.          EditorGUI.LabelField(firstRect, property.serializedObject.targetObject.ToString());
    29.          EditorGUI.PropertyField(secondRect, valueProperty, new GUIContent(property.displayName));
    30.       }
    31.    }
    32. }
    There might be some other bug in his code, or he means something different. :)

    upload_2022-12-27_9-57-5.png
     
  50. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,008
    Well, that's the point, it's still directly inside a UnityEngine.Object as all custom serializable classes are serialized as a continuous hierarchy inside a UnityEngine.Object. All those things directly belong to the UnityEngine.Object. Each serializedProperty has a property path that starts at the targetObject.

    I suggest opening the actual asset (if it's a prefab, open the prefab file, if it's in a scene, open the scene file) in a text editor. You will see that all the actual serialized values are just serialized below the UnityEngine.Object as a hierarchy tree without any type information.

    The new SerializeReference attribute work a bit different by somewhat "unrolling" / flattening the hierarchy into a list and allows cross references based on the internal id.