- Home /
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
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>();
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 =)
@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.
Sweet =), hopefully people upvote yours since its more complete.
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 ;)
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!
@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.
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.
@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?
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.
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.
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;
}
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.
How do you get the object from the PropertyDrawers OnGUI?
$$anonymous$$aybe some example code would help a lot of us with this problem.
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)
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...
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.
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.
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.
Your answer
Follow this Question
Related Questions
Arrays and Classes Javascript 1 Answer
C# finding gameobject that created this class, and do I need to clear them? 1 Answer
How do I check if a derived class is a certain class? 1 Answer
How do I check if a derived class is a certain class? 0 Answers
[CLOSED] How do I check if a derived class is a certain class? 0 Answers