Search Unity

Loop through SerializedProperty children

Discussion in 'Scripting' started by emrys90, Oct 7, 2016.

  1. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    How do you loop through all the children of a SerializedProperty? I've been struggling with this for a while now. I've tried looping using NextVisible(false), but that winds up returning a parent object instead of only all the children. I tried looping while the property was not the GetEndProperty, but that only returned the first two children instead of all of them.
     
  2. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Code (CSharp):
    1. SerializedProperty iterator = myProperty.Copy();
    2. // MyObject myObj = ScriptableObject.CreateInstance<MyObject>();
    3. // SerializedObject mySerializedObject = new UnityEditor.SerializedObject(myObj);
    4. // SerializedProperty iterator = mySerializedObject.FindProperty("PropertyName");
    5. while (it.Next(true))
    6. {
    7.       Debug.Log(it.name);
    8. }
     
    wayfarergames likes this.
  3. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    That would loop through all properties, including further going into children of the children. What I was looking for is to loop through all children of a property, only one level deep, and to not continue to sibling properties. I figured out a workable solution, for anyone else that it might benefit:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections.Generic;
    4.  
    5. public static class EditorExtensionMethods
    6. {
    7.     public static IEnumerable<SerializedProperty> GetChildren(this SerializedProperty property)
    8.     {
    9.         property = property.Copy();
    10.         var nextElement = property.Copy();
    11.         bool hasNextElement = nextElement.NextVisible(false);
    12.         if (!hasNextElement)
    13.         {
    14.             nextElement = null;
    15.         }
    16.  
    17.         property.NextVisible(true);
    18.         while (true)
    19.         {
    20.             if ((SerializedProperty.EqualContents(property, nextElement)))
    21.             {
    22.                 yield break;
    23.             }
    24.  
    25.             yield return property;
    26.  
    27.             bool hasNext = property.NextVisible(false);
    28.             if (!hasNext)
    29.             {
    30.                 break;
    31.             }
    32.         }
    33.     }
    34. }
    35.  
     
  4. PixelLifetime

    PixelLifetime

    Joined:
    Mar 30, 2017
    Posts:
    90
    Thanks, this has saved me some trouble.

    Here is what I have ended up with:

    Code (CSharp):
    1. /// <summary>
    2. /// Gets all children of `SerializedProperty` at 1 level depth.
    3. /// </summary>
    4. /// <param name="serializedProperty">Parent `SerializedProperty`.</param>
    5. /// <returns>Collection of `SerializedProperty` children.</returns>
    6. public static IEnumerable<SerializedProperty> GetChildren(this SerializedProperty serializedProperty)
    7. {
    8.     SerializedProperty currentProperty = serializedProperty.Copy();
    9.     SerializedProperty nextSiblingProperty = serializedProperty.Copy();
    10.     {
    11.         nextSiblingProperty.Next(false);
    12.     }
    13.  
    14.     if (currentProperty.Next(true))
    15.     {
    16.         do
    17.         {
    18.             if (SerializedProperty.EqualContents(currentProperty, nextSiblingProperty))
    19.                 break;
    20.  
    21.             yield return currentProperty;
    22.         }
    23.         while (currentProperty.Next(false));
    24.     }
    25. }
    26.  
    27. /// <summary>
    28. /// Gets visible children of `SerializedProperty` at 1 level depth.
    29. /// </summary>
    30. /// <param name="serializedProperty">Parent `SerializedProperty`.</param>
    31. /// <returns>Collection of `SerializedProperty` children.</returns>
    32. public static IEnumerable<SerializedProperty> GetVisibleChildren(this SerializedProperty serializedProperty)
    33. {
    34.     SerializedProperty currentProperty = serializedProperty.Copy();
    35.     SerializedProperty nextSiblingProperty = serializedProperty.Copy();
    36.     {
    37.         nextSiblingProperty.NextVisible(false);
    38.     }
    39.  
    40.     if (currentProperty.NextVisible(true))
    41.     {
    42.         do
    43.         {
    44.             if (SerializedProperty.EqualContents(currentProperty, nextSiblingProperty))
    45.                 break;
    46.  
    47.             yield return currentProperty;
    48.         }
    49.         while (currentProperty.NextVisible(false));
    50.     }
    51. }
     
  5. Acegikmo

    Acegikmo

    Joined:
    Jun 23, 2011
    Posts:
    1,294
    just a heads-up, might want to do
    yield return currentProperty.Copy();
    instead, otherwise functions like LINQ ToArray() won't work on this function!
     
  6. PixelLifetime

    PixelLifetime

    Joined:
    Mar 30, 2017
    Posts:
    90
    Hmm, what about the case when you get children and want to compare references? This might give some confusion to people using this function. Also, I am quite curious why it wouldn't work, do you have any references if that is not a trouble. Maybe, another function like
    `GetVisibleChildrenCopies`
    would work better.

    Anyway, this old API won't be of any use once we get stable UIToolkit :D
     
  7. nixcry

    nixcry

    Joined:
    Mar 13, 2017
    Posts:
    5
    For those still looking for the solution to this topic:
    Code (CSharp):
    1.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    2.     {
    3.         EditorGUI.BeginProperty(position, label, property);
    4.         var enumerator = property.GetEnumerator();
    5.         while (enumerator.MoveNext()) {
    6.             var prop = enumerator.Current as SerializedProperty;
    7.             if (prop == null) continue;
    8.             //Add your treatment to the current child property...
    9.             EditorGUILayout.PropertyField(prop);
    10.         }
    11.         EditorGUI.EndProperty();
    12.     }
    Reference: SerializedProperty.GetEnumerator
     
    ph3rin, haxic, CameronND and 2 others like this.
  8. ModLunar

    ModLunar

    Joined:
    Oct 16, 2016
    Posts:
    374
    I needed a solution for only direct children. I found this quick-and-dirty solution works:

    Note that since SerializedProperty has GetEnumerator(), we can run a foreach loop over it!

    Code (CSharp):
    1. private static IEnumerable<SerializedProperty> GetDirectChildren(SerializedProperty parent) {
    2.     int dots = parent.propertyPath.Count(c => c == '.');
    3.     foreach (SerializedProperty inner in parent) {
    4.         bool isDirectChild = inner.propertyPath.Count(c => c == '.') == dots + 1;
    5.         if (isDirectChild)
    6.             yield return inner;
    7.     }
    8. }
    Then elsewhere, you can call this like so:
    Code (CSharp):
    1. SerializedProperty property = //...
    2. foreach (SerializedProperty directChild in GetDirectChildren(property)) {
    3.     //...
    4. }
    I count the dots in the property paths and ignore any that have 2+ more dots than the original SerializedProperty.
    Direct children have exactly one (1) more dot operator in the path than the original SerializedProperty.
    At least I think.
    I wrote this quickly so hopefully I didn't forget any edge cases.
     
    jasonpierce and IndieForger like this.
  9. flashframe

    flashframe

    Joined:
    Feb 10, 2015
    Posts:
    797
    Sorry for bumping this thread but my solution to this was to use SerializedProperty.depth to only consider direct children:

    Code (CSharp):
    1.  
    2. IEnumerator enumerator = serializedProperty.GetEnumerator();
    3. int depth = serializedProperty.depth;
    4.  
    5. while (enumerator.MoveNext())
    6. {
    7.     SerializedProperty prop = enumerator.Current as SerializedProperty;
    8.     if (prop == null || prop.depth > depth + 1) continue;
    9.     //DO STUFF
    10. }
    It seems to work fine. But I'd be grateful to hear of any pitfalls of this approach.
     
  10. --julian95--

    --julian95--

    Joined:
    Nov 2, 2015
    Posts:
    6
    Just wanted to point out that you also have to call .Copy() on the SerializedProperties (children) that you want to return. Otherwise, the property always points to the last iterated property and not to the child that you added.

    My working extension function:

    Code (CSharp):
    1.  
    2.  
    3. public static IEnumerable<SerializedProperty> FindChildrenProperties(this SerializedProperty parent, int depth = 1) {
    4.     var depthOfParent = parent.depth;
    5.     var enumerator = parent.GetEnumerator();
    6.  
    7.     while (enumerator.MoveNext()) {
    8.         if (enumerator.Current is not SerializedProperty childProperty) continue;
    9.         if (childProperty.depth > depthOfParent + depth) continue;
    10.  
    11.         yield return childProperty.Copy();
    12.     }
    13. }
    14.  
    15.  
     
    Last edited: Aug 29, 2023