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

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*, obj);*

}
string fieldName = fieldStructure.Last();

return SetFieldOrPropertyValue(fieldName, obj, value);

}

///


/// Get the component of a serialized property
///

/// The property that is part of the component
/// The root component of the property
public static Component GetSerializedPropertyRootComponent(SerializedProperty property)
{
return (Component)property.serializedObject.targetObject;
}

///


/// Iterates through objects to handle objects that are nested in the root object
///

/// The type of the nested object
/// Path to the object through other properties e.g. PlayerInformation.Health
/// The root object from which this path leads to the property
/// Include base classes and interfaces as well
/// Returns the nested object casted to the type T
public static T GetNestedObject(string path, object obj, bool includeAllBases = false)
{
foreach (string part in path.Split(‘.’))
{
obj = GetFieldOrPropertyValue(part, obj, includeAllBases);
}
return (T)obj;
}

public static T GetFieldOrPropertyValue(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 GetBaseClassesAndInterfaces(this Type type, bool includeSelf = false)
{
List allTypes = new List();

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(true);

// Get the property value of debug
bool debugValue = serializedObject.FindProperty(“debug”).GetValue();

With the help of this

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.

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

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 :wink:

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

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.

@bellicapax

Any way for this to work with elements in a list? For example:

for (int i = 0; i < propertyArray.arraySize; i++)
        {
            var element = propertyArray.GetArrayElementAtIndex(i);
            var serializedSource = element.FindPropertyRelative("sourceName");

            CustomClass source = SerializedPropertyExtensions.GetValueFromScriptableObject<CustomClass>(serializedSource);

        }

Figured it out :slight_smile: This modification will get the value of any serialized property, including inside property drawers and array element properties.

    /// <summary>
    /// Get the object the serialized property of a Component 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 GetRootValue<T>(this SerializedProperty property, bool _includeBaseClasses = false)
    {
        var obj = GetSerializedPropertyRootObject(property);
        return GetNestedObject<T>(property.propertyPath, obj, _includeBaseClasses);
    }

    public static bool SetValueOnRoot<T>(this SerializedProperty property, T value)
    {

        object obj = GetSerializedPropertyRootObject(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*, obj);*

}
string fieldName = fieldStructure.Last();

return SetFieldOrPropertyValue(fieldName, obj, value);

}

///


/// Get the component of a serialized property
///

/// The property that is part of the component
/// The root component of the property
public static object GetSerializedPropertyRootObject(SerializedProperty property)
{
return property.serializedObject.targetObject;
}
///
/// Iterates through objects to handle objects that are nested in the root object
///

/// The type of the nested object
/// Path to the object through other properties e.g. PlayerInformation.Health
/// The root object from which this path leads to the property
/// Include base classes and interfaces as well
/// Returns the nested object casted to the type T
public static T GetNestedObject(string path, object obj, bool includeAllBases = false)
{
object nestedObj = null;
//store all array objs in a list for later
var arrayObjs = new List();
//split the path into separate fields
var split = path.Split(‘.’);
//find the field with the path
for (int i = 0; i < split.Length; i++)
{
//sometimes obj will come up as null in split loop for some reason?
if (obj != null)
{
//get the field
obj = GetFieldOrPropertyValue(split*, obj, includeAllBases);*
if (obj != null)
{
nestedObj = obj;
//if field came up as array, store the array
if (nestedObj.GetType().IsArray)
arrayObjs.Add(nestedObj);
}
}
}
//take the last array object and get the object[index]…this should find any nested field if within arrays
if (arrayObjs.Count > 0)
{
//get last array object
var lastArrayObj = arrayObjs[arrayObjs.Count - 1];
//only get array element if the desired return type is not the array itself
if (lastArrayObj.GetType() != typeof(T))
{

//get the final destination path
var lastPath = split[split.Length - 1];

//get index of the object in the last array and store it… source: http://sketchyventures.com/2015/08/07/unity-tip-getting-the-actual-object-from-a-custom-property-drawer/*_
var index = Convert.ToInt32(new string(path.Where(c => char.IsDigit(c)).ToArray()));
var array = lastArrayObj as object[];
if (index <= array.Length - 1)
_
{*

nestedObj = array[index];
//find field in stored object
nestedObj = GetFieldOrPropertyValue(lastPath, nestedObj, includeAllBases);
}
}
}
return (T)nestedObj;

}

public static T GetFieldOrPropertyValue(string fieldName, object obj, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where T:class
{
var fieldType = obj.GetType();
if (fieldType != null)
{
FieldInfo field = fieldType.GetField(fieldName, bindings);
if (field != null)
{
var fieldObj = field.GetValue(obj);
return (T)fieldObj;
}
}

PropertyInfo property = obj.GetType().GetProperty(fieldName, bindings);
if (property != null)
{
return (T)property.GetValue(obj, null);
}

var prop = obj as T;
if (prop != null)
{
return prop;
}

if (includeAllBases)
{

foreach (Type type in GetBaseClassesAndInterfaces(obj.GetType()))
{
var 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);
}

I think you’re going to have a problem with the fact that both MethodInfo and System.Object (object) aren’t serializable.

As for accessing lists/arrays that are represented as a SerializedProperty. ttrockstars login

There are methods on SerializedProperty to treat them like a list. ‘arraySize’, ‘GetArrayElementAtIndex’, etc. See the documentation:

When done you’d say ‘ApplyModifiedProperties’.