Skip to content

Instantly share code, notes, and snippets.

@douduck08
Last active February 29, 2024 09:03
Show Gist options
  • Star 49 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save douduck08/6d3e323b538a741466de00c30aa4b61f to your computer and use it in GitHub Desktop.
Save douduck08/6d3e323b538a741466de00c30aa4b61f to your computer and use it in GitHub Desktop.
The general GetValue extension of SerializedProperty in Unity Editor.

Unity's SerializedProperty not support custom type value setting. This extension use Reflection to get target instance of SerializedProperty in Custom Editor, made value setting of general type is posible.

// <author>
// douduck08: https://github.com/douduck08
// Use Reflection to get instance of Unity's SerializedProperty in Custom Editor.
// Modified codes from 'Unity Answers', in order to apply on nested List<T> or Array.
//
// Original author: HiddenMonk & Johannes Deml
// Ref: http://answers.unity3d.com/questions/627090/convert-serializedproperty-to-custom-class.html
// </author>
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
public static class SerializedPropertyExtension
{
static readonly Regex rgx = new Regex(@"\[\d+\]", RegexOptions.Compiled);
public static T GetValue<T>(this SerializedProperty property) where T : class
{
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
for (int i = 0; i < fieldStructure.Length; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
return (T)obj;
}
public static bool SetValue<T>(this SerializedProperty property, T value) where T : class
{
object obj = property.serializedObject.targetObject;
string path = property.propertyPath.Replace(".Array.data", "");
string[] fieldStructure = path.Split('.');
for (int i = 0; i < fieldStructure.Length - 1; i++)
{
if (fieldStructure[i].Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldStructure[i].Where(c => char.IsDigit(c)).ToArray()));
obj = GetFieldValueWithIndex(rgx.Replace(fieldStructure[i], ""), obj, index);
}
else
{
obj = GetFieldValue(fieldStructure[i], obj);
}
}
string fieldName = fieldStructure.Last();
if (fieldName.Contains("["))
{
int index = System.Convert.ToInt32(new string(fieldName.Where(c => char.IsDigit(c)).ToArray()));
return SetFieldValueWithIndex(rgx.Replace(fieldName, ""), obj, index, value);
}
else
{
return SetFieldValue(fieldName, obj, value);
}
}
private static object GetFieldValue(string fieldName, object obj, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
return field.GetValue(obj);
}
return default(object);
}
private static object GetFieldValueWithIndex(string fieldName, object obj, int index, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
{
FieldInfo field = obj.GetType().GetField(fieldName, bindings);
if (field != null)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
return ((object[])list)[index];
}
else if (list is IEnumerable)
{
return ((IList)list)[index];
}
}
return default(object);
}
public static bool SetFieldValue(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;
}
return false;
}
public static bool SetFieldValueWithIndex(string fieldName, object obj, int index, 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)
{
object list = field.GetValue(obj);
if (list.GetType().IsArray)
{
((object[])list)[index] = value;
return true;
}
else if (list is IEnumerable)
{
((IList)list)[index] = value;
return true;
}
}
return false;
}
}
@knl97vladimir
Copy link

Very useful script! Thank you very much!

@KumoKyaku
Copy link

https://gist.github.com/douduck08/6d3e323b538a741466de00c30aa4b61f#file-serializedpropertyextensions-cs-L97
} else if (value is IEnumerable) {
Is this a typo?
else if (list is IEnumerable)

@douduck08
Copy link
Author

Is this a typo?

Looks like be yes.
But value and list both should be IEnumerable, so it's still work.
Thanks.

@knl97vladimir
Copy link

knl97vladimir commented Apr 18, 2023

I advise you to move the Regex to a separate static variable to improve performance by about 4 times.
Like this:
private static readonly Regex rgx = new Regex(@"\[\d+\]", RegexOptions.Compiled);
P.S. RegexOptions.Compiled might give a performance improvement, but I'm not sure.

@douduck08
Copy link
Author

I advise you to move the Regex to a separate static variable to improve performance by about 4 times. Like this: private static readonly Regex rgx = new Regex(@"\[\d+\]", RegexOptions.Compiled); P.S. RegexOptions.Compiled might give a performance improvement, but I'm not sure.

Good idea!

@BigBlueDraco
Copy link

You are legend!!!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment