Skip to content

Commit 842ce13

Browse files
committed
Serialization problems with inherited properties solved
1 parent 63130cd commit 842ce13

File tree

6 files changed

+134
-55
lines changed

6 files changed

+134
-55
lines changed

Assets/TableForge/Editor/Core/Data/Generation/ItemSerialization/IFieldSerializationStrategy.cs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Reflection;
4+
using UnityEngine;
45

56
namespace TableForge.Editor
67
{
@@ -11,17 +12,24 @@ internal interface IFieldSerializationStrategy
1112

1213
internal class BaseFieldSerializationStrategy : IFieldSerializationStrategy
1314
{
14-
public List<TfFieldInfo> GetFields(Type type)
15+
public List<TfFieldInfo> GetFields(Type baseType)
1516
{
17+
HashSet<string> fields = new HashSet<string>();
1618
List<TfFieldInfo> members = new List<TfFieldInfo>();
17-
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
19+
20+
foreach (var type in baseType.GetBaseTypes(breakType: typeof(ScriptableObject)))
1821
{
19-
if (!SerializationUtil.IsSerializable(field)) continue;
20-
if (SerializationUtil.HasCircularDependency(field.FieldType, type)) continue;
21-
22-
string friendlyName = SerializationUtil.GetFriendlyName(field);
23-
24-
members.Add(new TfFieldInfo(field.Name, friendlyName, type, field.FieldType));
22+
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
23+
{
24+
if (fields.Contains(field.Name)) continue;
25+
if (!SerializationUtil.IsSerializable(field)) continue;
26+
if (SerializationUtil.HasCircularDependency(field.FieldType, type)) continue;
27+
28+
string friendlyName = SerializationUtil.GetFriendlyName(field);
29+
30+
members.Add(new TfFieldInfo(field.Name, friendlyName, type, field.FieldType));
31+
fields.Add(field.Name); // Add field to the set to avoid duplicates
32+
}
2533
}
2634

2735
return members;
@@ -30,17 +38,24 @@ public List<TfFieldInfo> GetFields(Type type)
3038

3139
internal class PrivateIncludedFieldSerializationStrategy : IFieldSerializationStrategy
3240
{
33-
public List<TfFieldInfo> GetFields(Type type)
41+
public List<TfFieldInfo> GetFields(Type baseType)
3442
{
43+
HashSet<string> fields = new HashSet<string>();
3544
List<TfFieldInfo> members = new List<TfFieldInfo>();
36-
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
45+
46+
foreach (var type in baseType.GetBaseTypes(breakType: typeof(ScriptableObject)))
3747
{
38-
if (!SerializationUtil.IsSerializable(field, true)) continue;
39-
if (SerializationUtil.HasCircularDependency(field.FieldType, type)) continue;
40-
41-
string friendlyName = SerializationUtil.GetFriendlyName(field);
42-
43-
members.Add(new TfFieldInfo(field.Name, friendlyName, type, field.FieldType));
48+
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
49+
{
50+
if (fields.Contains(field.Name)) continue;
51+
if (!SerializationUtil.IsSerializable(field, true)) continue;
52+
if (SerializationUtil.HasCircularDependency(field.FieldType, type)) continue;
53+
54+
string friendlyName = SerializationUtil.GetFriendlyName(field);
55+
56+
members.Add(new TfFieldInfo(field.Name, friendlyName, type, field.FieldType));
57+
fields.Add(field.Name); // Add field to the set to avoid duplicates
58+
}
4459
}
4560

4661
return members;

Assets/TableForge/Editor/Core/Utilities/Extensions/TypeExtension.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,31 @@ public static bool IsListOrArrayType(this Type type)
102102

103103
#region Reflection Methods
104104

105+
/// <summary>
106+
/// Retrieves all base types of a given type, including the type itself.
107+
/// </summary>
108+
public static List<Type> GetBaseTypes(this Type type, bool includeSelf = true, Type breakType = null)
109+
{
110+
if (type == null)
111+
throw new ArgumentNullException(nameof(type), "Type cannot be null.");
112+
113+
var baseTypes = new List<Type>();
114+
if (includeSelf) baseTypes.Add(type);
115+
116+
Type current = type.BaseType;
117+
while (current != null)
118+
{
119+
if (breakType != null && current == breakType)
120+
break;
121+
122+
baseTypes.Insert(0, current);
123+
current = current.BaseType;
124+
}
125+
126+
return baseTypes;
127+
}
128+
129+
105130
/// <summary>
106131
/// Retrieves the member information of a given property path in a type.
107132
/// </summary>

Assets/TableForge/Editor/PersistentData/TestData.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/TableForge/Editor/Tests/UnitTests/SerializationTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ public void GetSerializableFields()
3535
Assert.IsTrue(fields.Any(x => x.Name == "vector3Array"));
3636
Assert.IsTrue(fields.Any(x => x.Name == "sampleEnum"));
3737
Assert.IsTrue(fields.Any(x => x.Name == "stringToIntDictionary"));
38+
Assert.IsTrue(fields.Any(x => x.Name == "<InheritedIntProperty>k__BackingField"));
39+
Assert.IsTrue(fields.Any(x => x.Name == "<InterfaceInt>k__BackingField"));
40+
Assert.IsTrue(fields.Any(x => x.Name == "<AbstractInt>k__BackingField"));
41+
Assert.IsTrue(fields.Any(x => x.Name == "<AbstractString>k__BackingField"));
42+
Assert.IsTrue(fields.Any(x => x.Name == "<VirtualInt>k__BackingField"));
43+
Assert.IsTrue(fields.Any(x => x.Name == "inheritedInt"));
3844

3945
//Check if the fields non-serializable fields are not included
4046
Assert.IsTrue(fields.All(x => x.Name != "_privateIntField"));
@@ -45,6 +51,8 @@ public void GetSerializableFields()
4551
Assert.IsTrue(fields.All(x => x.Name != "intList2D"));
4652
Assert.IsTrue(fields.All(x => x.Name != "stringToNestedDataDictionary"));
4753
Assert.IsTrue(fields.All(x => x.Name != "nestedDataToIntDictionary"));
54+
Assert.IsTrue(fields.Any(x => x.Name != "<AbstractFloat>k__BackingField"));
55+
4856
}
4957

5058
}

Assets/TableForge/Editor/Tests/Utils/ScriptableObjectScripts/SampleTestData.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
namespace TableForge.Tests
88
{
9-
internal class SampleTestData : ScriptableObject
9+
10+
[CreateAssetMenu(fileName = "SampleTestData", menuName = "TableForge/Tests/Sample Test Data")]
11+
internal class SampleTestData : AbstractTestData, ITestInterface
1012
{
1113
#region Basic Fields
1214

@@ -94,6 +96,11 @@ public enum SampleEnum
9496
};
9597

9698
#endregion
99+
100+
[field: SerializeField] public int InterfaceInt { get; set; }
101+
[field: SerializeField] public override int AbstractInt { get; protected set; } = 3;
102+
[field: SerializeField] public override string AbstractString { get; set; }
103+
[field: SerializeField] public override float AbstractFloat { get; } = 5;
97104
}
98105

99106
[Serializable]
@@ -125,4 +132,20 @@ public NestedData(int number, string text)
125132

126133
#endregion
127134
}
128-
}
135+
136+
internal abstract class AbstractTestData : ScriptableObject
137+
{
138+
public int inheritedInt;
139+
[field: SerializeField] public int InheritedIntProperty { get; set; } = 42;
140+
141+
public abstract int AbstractInt { get; protected set; }
142+
public abstract string AbstractString { get; set; }
143+
public abstract float AbstractFloat { get; }
144+
[field: SerializeField] public virtual int VirtualInt { get; set; } = 42;
145+
}
146+
147+
internal interface ITestInterface
148+
{
149+
int InterfaceInt { get; set; }
150+
}
151+
}

0 commit comments

Comments
 (0)