Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
13 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
3
Question by Ziboo · Jan 28, 2014 at 12:32 PM · class objectclass instance

Convert SerializedProperty to Custom Class

Hi,

I struggling with SerilizedProperty.

Let's say I have class

 [System.Serializable]
 public class ItemRandom
 {
         public bool test = false;
         public int test2 = 4;
 }

And I want to save them like preset in a list of ScriptableObject

 [System.Serializable]
 public class PresetHolder : ScriptableObject
 {
         public List<ItemRandom> randomPresets = new List<ItemRandom> ();
 
         
         public void AddRandomPreset (SerializedProperty random)
         {
             -- How can I had to the list the random property as ItemRandom type
         }
 }

How can I convert a SerializeProperty in a custom class ?

I looked everywhere, the only thing I found is maybe to derived the class from ScriptableObject to and create instances of them, but I would like to avoid that...

Thanks

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

7 Replies

· Add your reply
  • Sort: 
avatar image
13

Answer by Johannski · Mar 12, 2016 at 05:52 PM

This is a refinement of @HiddenMonk 's code, with which you can easily get and set a field or property of a serializedProperty.

Just create a class called SerializedPropertyExtensions in a Folder with the name Editor with this code:

 // --------------------------------------------------------------------------------------------------------------------
 // <author>
 //   HiddenMonk
 //   http://answers.unity3d.com/users/496850/hiddenmonk.html
 //   
 //   Johannes Deml
 //   send@johannesdeml.com
 // </author>
 // --------------------------------------------------------------------------------------------------------------------
 
 using System.Collections.Generic;
 using System.Linq;
 
 namespace Supyrb 
 {
     using System;
     using UnityEngine;
     using UnityEditor;
     using System.Reflection;
 
     /// <summary>
     /// Extension class for SerializedProperties
     /// See also: http://answers.unity3d.com/questions/627090/convert-serializedproperty-to-custom-class.html
     /// </summary>
     public static class SerializedPropertyExtensions 
     {
         /// <summary>
         /// Get the object the serialized property holds by using reflection
         /// </summary>
         /// <typeparam name="T">The object type that the property contains</typeparam>
         /// <param name="property"></param>
         /// <returns>Returns the object type T if it is the type the property actually contains</returns>
         public static T GetValue<T>(this SerializedProperty property)
         {
             return GetNestedObject<T>(property.propertyPath, GetSerializedPropertyRootComponent(property));
         }
 
         /// <summary>
         /// Set the value of a field of the property with the type T
         /// </summary>
         /// <typeparam name="T">The type of the field that is set</typeparam>
         /// <param name="property">The serialized property that should be set</param>
         /// <param name="value">The new value for the specified property</param>
         /// <returns>Returns if the operation was successful or failed</returns>
         public static bool SetValue<T>(this SerializedProperty property, T value)
         {
             
             object obj = GetSerializedPropertyRootComponent(property);
             //Iterate to parent object of the value, necessary if it is a nested object
             string[] fieldStructure = property.propertyPath.Split('.');
             for (int i = 0; i < fieldStructure.Length - 1; i++)
             {
                 obj = GetFieldOrPropertyValue<object>(fieldStructure[i], obj);
             }
             string fieldName = fieldStructure.Last();
 
             return SetFieldOrPropertyValue(fieldName, obj, value);
             
         }
 
         /// <summary>
         /// Get the component of a serialized property
         /// </summary>
         /// <param name="property">The property that is part of the component</param>
         /// <returns>The root component of the property</returns>
         public static Component GetSerializedPropertyRootComponent(SerializedProperty property)
         {
             return (Component)property.serializedObject.targetObject;
         }
 
         /// <summary>
         /// Iterates through objects to handle objects that are nested in the root object
         /// </summary>
         /// <typeparam name="T">The type of the nested object</typeparam>
         /// <param name="path">Path to the object through other properties e.g. PlayerInformation.Health</param>
         /// <param name="obj">The root object from which this path leads to the property</param>
         /// <param name="includeAllBases">Include base classes and interfaces as well</param>
         /// <returns>Returns the nested object casted to the type T</returns>
         public static T GetNestedObject<T>(string path, object obj, bool includeAllBases = false)
         {
             foreach (string part in path.Split('.'))
             {
                 obj = GetFieldOrPropertyValue<object>(part, obj, includeAllBases);
             }
             return (T)obj;
         }
 
         public static T GetFieldOrPropertyValue<T>(string fieldName, object obj, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
         {
             FieldInfo field = obj.GetType().GetField(fieldName, bindings);
             if (field != null) return (T)field.GetValue(obj);
 
             PropertyInfo property = obj.GetType().GetProperty(fieldName, bindings);
             if (property != null) return (T)property.GetValue(obj, null);
 
             if (includeAllBases)
             {
 
                 foreach (Type type in GetBaseClassesAndInterfaces(obj.GetType()))
                 {
                     field = type.GetField(fieldName, bindings);
                     if (field != null) return (T)field.GetValue(obj);
 
                     property = type.GetProperty(fieldName, bindings);
                     if (property != null) return (T)property.GetValue(obj, null);
                 }
             }
 
             return default(T);
         }
 
         public static bool SetFieldOrPropertyValue(string fieldName, object obj, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
         {
             FieldInfo field = obj.GetType().GetField(fieldName, bindings);
             if (field != null)
             {
                 field.SetValue(obj, value);
                 return true;
             }
 
             PropertyInfo property = obj.GetType().GetProperty(fieldName, bindings);
             if (property != null)
             {
                 property.SetValue(obj, value, null);
                 return true;
             }
 
             if (includeAllBases)
             {
                 foreach (Type type in GetBaseClassesAndInterfaces(obj.GetType()))
                 {
                     field = type.GetField(fieldName, bindings);
                     if (field != null)
                     {
                         field.SetValue(obj, value);
                         return true;
                     }
 
                     property = type.GetProperty(fieldName, bindings);
                     if (property != null)
                     {
                         property.SetValue(obj, value, null);
                         return true;
                     }
                 }
             }
             return false;
         }
 
         public static IEnumerable<Type> GetBaseClassesAndInterfaces(this Type type, bool includeSelf = false)
         {
             List<Type> allTypes = new List<Type>();
 
             if (includeSelf) allTypes.Add(type);
 
             if (type.BaseType == typeof(object))
             {
                 allTypes.AddRange(type.GetInterfaces());
             }
             else {
                 allTypes.AddRange(
                         Enumerable
                         .Repeat(type.BaseType, 1)
                         .Concat(type.GetInterfaces())
                         .Concat(type.BaseType.GetBaseClassesAndInterfaces())
                         .Distinct());
             }
 
             return allTypes;
         }
     }
 }

You can then call the functions like this:

 // Get a serialized object
 var serializedObject = new UnityEditor.SerializedObject(target);
 
 // Set the property debug to true
 serializedObject.FindProperty("debug").SetValue<bool>(true);
 
 // Get the property value of debug
 bool debugValue = serializedObject.FindProperty("debug").GetValue<bool>();




Comment
Add comment · Show 31 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image HiddenMonk · Mar 13, 2016 at 01:47 AM 0
Share

I have just updated my post with a fix to an issue with capturing private inherited variables. Basically we needed to search each BaseClass for those private variables. I also added a Set method as I found I needed that as well.

Perhaps you can update your code with my update and refactor / make it nicer looking =)

avatar image Johannski HiddenMonk · Mar 13, 2016 at 03:26 PM 0
Share

@Hidden$$anonymous$$onk : I extendend your solution quite a bit to offer the the same ease (if not more) to set a value of a property.

avatar image HiddenMonk · Mar 13, 2016 at 03:40 PM 0
Share

Sweet =), hopefully people upvote yours since its more complete.

avatar image Johannski HiddenMonk · Mar 13, 2016 at 03:44 PM 0
Share

Even though it felt really, really wrong I removed my upvote from your comment, so now it is at the top. I will upvote your solution once I've got enough upvotes to stay on top ;)

avatar image Blizzard_jedi · May 16, 2016 at 10:41 PM 1
Share

Thank you so much, guys! Your solution really helped me a lot!

Btw, still wondering, how the Unity Tech managed to leave this thing as it is now. SerializedProperty is so raw and unfriendly to use!

avatar image HiddenMonk · Aug 18, 2016 at 07:23 AM 1
Share

@patrick-noten it is just a way to organize code. $$anonymous$$ake sure you are adding "using Supyrb" at the top of your code file like how you do "using UnityEngine" and what not. It isnt needed though.

avatar image TheHeftyCoder · May 13, 2019 at 04:08 PM 1
Share

This may be late, but I'm getting InvalidCastExceptions when trying to use this with basically anything (I'm using it in a PropertyDrawer). Should it only be used for an Editor?

Can't seem to make this work at all, I get either NullReferenceException or InvalidCastException. The object obj parameter that many of the functions use frequently returns null.

EDIT: Somehow made it work by combining the comment above me as well as making GetSerializedPropertyRootComponent work for ScriptableObjects as well, which you can find in the comments below.

avatar image owen_proto TheHeftyCoder · Jul 07, 2019 at 06:38 AM 0
Share

@TheHeftyCoder The scriptable object fixed worked nicely for me, but the list fix from @Vonwarr doesn't compile with the error being that IList needs a type argument. I tried using the generic T but that throws an invalid cast error. How did you get it to work?

Show more comments
avatar image
3

Answer by HiddenMonk · Mar 01, 2016 at 05:51 PM

With the help of this http://stackoverflow.com/questions/1954746/using-reflection-in-c-sharp-to-get-properties-of-a-nested-object I was able to get the actual class object from the serializedProperty

The idea was to first get the actual main component my serializedProperty was on and then to use the serializedProperty.propertyPath with C# reflection to get the object.

Heres the code, but keep in mind I have not tested it a lot and since I dont know much about reflection, I might be doing things wrong.

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

 //Put these methods in whatever static class youd like =)

 public static T SerializedPropertyToObject<T>(SerializedProperty property)
 {
     return GetNestedObject<T>(property.propertyPath, GetSerializedPropertyRootComponent(property), true); //The "true" means we will also check all base classes
 }
 
 public static Component GetSerializedPropertyRootComponent(SerializedProperty property)
 {
     return (Component)property.serializedObject.targetObject;
 }
 
 public static T GetNestedObject<T>(string path, object obj, bool includeAllBases = false)
 {
     foreach(string part in path.Split('.'))
     {
         obj = GetFieldOrPropertyValue<object>(part, obj, includeAllBases);
     }
     return (T)obj;
 }
 
 public static T GetFieldOrPropertyValue<T>(string fieldName, object obj, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
 {
     FieldInfo field = obj.GetType().GetField(fieldName, bindings);
     if(field != null) return (T)field.GetValue(obj);
 
     PropertyInfo property = obj.GetType().GetProperty(fieldName, bindings);
     if(property != null) return (T)property.GetValue(obj, null);
 
     if(includeAllBases)
     {
 
         foreach(Type type in GetBaseClassesAndInterfaces(obj.GetType()))
         {
             field = type.GetField(fieldName, bindings);
             if(field != null) return (T)field.GetValue(obj);
 
             property = type.GetProperty(fieldName, bindings);
             if(property != null) return (T)property.GetValue(obj, null);
         }
     }
 
     return default(T);
 }
 
 public static void SetFieldOrPropertyValue<T>(string fieldName, object obj, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
 {
     FieldInfo field = obj.GetType().GetField(fieldName, bindings);
     if(field != null)
     {
         field.SetValue(obj, value);
         return;
     }
 
     PropertyInfo property = obj.GetType().GetProperty(fieldName, bindings);
     if(property != null)
     {
         property.SetValue(obj, value, null);
         return;
     }
 
     if(includeAllBases)
     {
         foreach(Type type in GetBaseClassesAndInterfaces(obj.GetType()))
         {
             field = type.GetField(fieldName, bindings);
             if(field != null)
             {
                 field.SetValue(obj, value);
                 return;
             }
 
             property = type.GetProperty(fieldName, bindings);
             if(property != null)
             {
                 property.SetValue(obj, value, null);
                 return;
             }
         }
     }
 }
 
 public static IEnumerable<Type> GetBaseClassesAndInterfaces(this Type type, bool includeSelf = false)
 {
     List<Type> allTypes = new List<Type>();
 
     if(includeSelf) allTypes.Add(type);
 
     if(type.BaseType == typeof(object))
     {
         allTypes.AddRange(type.GetInterfaces());
     }else{
         allTypes.AddRange(
                 Enumerable
                 .Repeat(type.BaseType, 1)
                 .Concat(type.GetInterfaces())
                 .Concat(type.BaseType.GetBaseClassesAndInterfaces())
                 .Distinct());
              //I found this on stackoverflow
     }
 
     return allTypes;
 }


So in regards to the original post, to get the ItemRandom you would do

 ItemRandom itemRandom = SerializedPropertyToObject<ItemRandom>(serializedProperty);

(the original post has the SerializedProperty parameter named as "random", but in my example above I just pretended it was named "serializedProperty" for clarity)

Keep in mind that if you want to set the property you are getting, you need to use the SetFieldOrPropertyValue. What I mean by this is, lets say the original posters ItemRandom class has a float inside it that you want to change and you do this - itemRandom.someFloat = 4; - then that is fine and you dont need to use the SetFieldOrPropertyValue method. However, if you are trying to do this - itemRandom = aDifferentItemRandom, then you are going to need to use the SetFieldOrPropertyValue.

Also, the reason we need to search each base class explicitly is to make sure we are able to capture private inherited fields or properties.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image
1

Answer by bellicapax · Aug 10, 2018 at 05:07 PM

If anyone wants a version of @Johannski 's refinement of @HiddenMonk 's answer that also works for ScriptableObjects, here ya go.

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image toomasio · Nov 03, 2018 at 02:40 AM 0
Share

managed to avoid getting those nasty invalid casting errors by doing this:

 public static object GetSerializedPropertyRootObject(SerializedProperty property)
     {
         var tar = property.serializedObject.targetObject;
         object obj = tar as Component;
         if (obj != null)
             return obj;
         obj = tar as ScriptableObject;
         if (obj != null)
             return obj;
         Debug.LogError("Could not get target object on " + property.displayName);
         return null;
     }
avatar image
0

Answer by Clet_ · Apr 10, 2014 at 12:37 AM

Sorry to be late for the party.

I've encountered the same problem in my custom editor class. The trick is to use C# Reflection.

 using System.Reflection;
 
 public static T GetFieldByName<T>(string fieldName, BindingFlags bindingFlags, object obj)
 {
     FieldInfo fieldInfo = obj.GetType ().GetField (fieldName, bindingFlags);
         
     if(fieldInfo == null)
         return default(T);
         
     return (T)fieldInfo.GetValue (obj);
 }

T is the type of the property you're attempting to get, fieldName is the name you gave it in your class, bindingFlags depends on what is the field modifier, and obj is the object which holds the property.

If you want to set a value to this property, you could want to take a look at the FieldInfo class.

Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image thienhaflash · Jan 03, 2015 at 12:53 AM 1
Share

I don't see how it's related to the question :|

avatar image tomtominc · Sep 11, 2015 at 07:53 PM 0
Share

How do you get the object from the PropertyDrawers OnGUI?

avatar image tomtominc · Sep 11, 2015 at 07:54 PM 0
Share

$$anonymous$$aybe some example code would help a lot of us with this problem.

avatar image
0

Answer by Bunny83 · Jan 28, 2014 at 12:48 PM

Since your ItemRandom class is a referenceType you just have to access objectReferenceValue and cast it to the right type. However by careful, in your code above it seems you try to use a SerializedProperty inside of a runtime script. SerializedProperty is an editor class and can only be used in editor classes. If you use the UnityEditor namespace in runtime scripts you can't build your game / application since this namespace doesn't exist outside of the Unity editor.

If you want to put some editor functions inside the class itself, you have to wrap everything related to "UnityEditor" in preprocessor tags:

     #if UNITY_EDITOR
     //Some code that uses UnityEditor which is stripped from the final build
     #endif

edit
I like to add that SerializedObject and SerializedProperty are mainly used for displaying the ínspector GUI. Everything that is actually serialized is already covered by SerializedProperties. The first serialized property works like an iterator. You can call the Next method to go to the next property as you see them in the inspector.

Keep in mind that your custom class doesn't exist in the sens of the serialization system. All your fields in your custom class are serialized in a linear list of properties. Each field (which is serialized / serializable) is represented by it's own SerializedProperty.

So if you have a SerializedProperty object which represents your "randomPresets" list, if you call Next(true) the SerializedProperty would now represent the first ItemRandom in that list. If you call Next(true) again it would represent the field "test" of your first ItemRandom instance. They come out in the same order you see them in the inspector.

final note: It's usually better to explain what exactly you want to do instead of picking a possible solution and ask how this "solution" works ;)

Good luck, (with whatever you're trying to do)

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Ziboo · Jan 28, 2014 at 12:55 PM 0
Share

Hi,

Thanks for the quick answer. I'm only using it in the editor ;)

I tried it and got that:

type is not a supported pptr value

it doesn't seem to recognize the random class instance as an object...

avatar image Bunny83 · Jan 28, 2014 at 01:13 PM 0
Share

Yes, my bad ;) It's been a while since i use the SerializedObject. objectReferenceValue only covers classes which are derived from UnityEngine.Object. So this would work what you have references to Components or gameobjects and the like. As far as i know you can't get the reference to the actual instance of a custom class.

The SerializedObject represents the serialized data, not the actual instance. Since the data is serialized along with the Scriptable object you only have access to the individual value-type fields in a more or less "flattened" hierarchy.

avatar image Ziboo · Jan 28, 2014 at 01:17 PM 0
Share

Yes I think I was misunderstood.

I'm doing a editor tool. I have properties of ItemRandom in different classes, I display them into an editor inspector class with SerializedProperty.

But I would like to make presets of this class, save it in an array somewhere (ScriptableObject) to use it later if I want.

It would be easier to access directly the ItemRandom field of my object but the thing is I'm using PropertiesDrawer to draw the ItemRandom Class, so I have only acces to the SerializedProperty when I would like to save it somewhere.

avatar image Ziboo · Jan 29, 2014 at 08:18 AM 0
Share

So no solution ? :(

avatar image Bunny83 · Jan 31, 2014 at 02:46 AM 0
Share

No, sorry, but like already said the SerializedProperty wraps a single property. For the serialization system your "ItemRandom" class doesn't even exist. The fields of your ItemRandom class (test and test2) are serialized like they are part of the containing ScriptableObject / $$anonymous$$onoBehaviour.

Without seeing your code we can't suggest a solution. Fact is: you can't turn a SerializedProperty into a ItemRandom class since they aren't directly related.

  • 1
  • 2
  • ›

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

34 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How to declare multiple variables using a for loop in class declaration? 0 Answers

Runtime Error IOS AOT Only 0 Answers

Arrays and Classes javascript 1 Answer

Why have error: NullReferenceException: Object reference not set to an instance of an object 1 Answer

Arrays and Classes Javascript 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges