Unity 2019.3b1 [SerializeReference] data is stored by class name. If the class name changes, the reference will be lost.
Is it possible to store by GUID instead of the class name? Or have other better ways to prevent loss?
Good catch. Unity Technologies, please support a mechanism that allows us to refactor code without breaking assets.
There isn’t a GUID for a type. Maybe support FormerlySerializedAs on class?
Add a restriction: if the class wants to be referenced by [SerializeReference], the class must correspond to a script. Just like a MonoBehaviour.
Please file a bug report so we can track the problem, and we’ll look at what we want to do about it.
Here is some bacon for you:
(Case 1180719) [SerializeReference] data loss when class name is changed
QA was able to reproduce the issue and it can be found in the public issue tracker now:
Thanks!
I tested in 2020.1.0a15, serialization error when renaming class Jump1 to Jump2:
renaming class Jump1 to Jump2
If you’re still encountering issues like this, could you please submit a bug report with a reproduction project? The case that Peter submitted has been fixed in 2020.1.0a10.
(Case 1204407) [SerializeReference] serialized data loss when class name is changed
Unity2020.1.0b1 can still reproduce the problem.
5600734–579373–Assets.zip (2.85 KB)
Issue Tracker
where did that issue go ?
https://issuetracker.unity3d.com/is…e-data-lost-when-the-class-name-is-refactored
I have the same thing with a struct.
Same problem
Is there a workaround for when a managedReferenceValue class has been deleted? I can identify when the value is null but replacing does not apply changes and they get reverted on assembly reload. The serializedObject.target seems to be corrupted, also setting any field on target does not apply changes. Target is a ScriptableObject. If target is MonoBehavior i can replace null referenced values.
Error on assembly reload: Unknown managed type referenced: [Assembly-CSharp] + Type
Unity 2019.4.10
/*
* I can't apply any changes to managedReferenceValue if it is null
* for (int i = 0; i < this.m_Actions.arraySize; i++) {
SerializedProperty element = this.m_Actions.GetArrayElementAtIndex(i);
if (element.GetValue() == null) {
element.managedReferenceValue = new MissingAction();
serializedObject.ApplyModifiedPropertiesWithoutUndo();
}
}*/
For anyone getting this, the comment in the issue tracker suggests a fix:
Add the MovedFrom
attribute on top of your class:
using UnityEngine.Scripting.APIUpdating;
namespace NewNamespace
{
[MovedFrom(false, null, "OldNamespace", "OldClass")]
public class NewClass
{
}
}
If you haven’t changed the namespace, you can leave the third parameter null, it seems.
If you have changed the assembly, you can fill in the first parameter with the old assembly name (untested).
It works
How to deal with deleted classes? The scriptableObject containing this invalid assignment is not correctly loaded if the class is missing. Any change to it is reverted on assembly reload, every field in the inspector is displayed as null, but the serialized data still contains the old values.
Unfortunately you can only fix this by removing the broken components and adding them anew.
So lets do it!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
public class MyClass : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeReference] protected IMyInterface myInterface;
private string lastType;
public void OnBeforeSerialize()
{
lastType = myInterface?.GetType().AssemblyQualifiedName; // This measure to prevent our solution being called when myInterface is actually null, which would mess with Undo operations.
}
private const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Default | BindingFlags.DeclaredOnly | BindingFlags.Instance;
public void OnAfterDeserialize()
{
#if UNITY_EDITOR
if(!string.IsNullOrEmpty(lastType) && System.Type.GetType(lastType) == null)
{
UnityEditor.EditorApplication.delayCall += () =>
{
var go = gameObject;
DestroyImmediate(this); // Destroying first handles cases with DisallowMultipleComponent.
// The next part looks scary if you're not versed in reflection, but in a nutshell it will copy all of our serialized data into the copy component.
// Create a new component of the same type.
var componentType = GetType();
var component = go.AddComponent(componentType);
// Get all the fields from that component (up to MonoBehaviour otherwise we might mess up some internal stuff, IDs being mixed, big hassle).
IEnumerable<FieldInfo> finfos = Enumerable.Empty<FieldInfo>();
do
{
finfos = finfos.Concat(componentType.GetFields(bindingFlags));
componentType = componentType.BaseType;
}
while (componentType != typeof(MonoBehaviour));
// Do some magic to make sure we only get serializable fields.
finfos = from field in finfos
let attributeTypes = field.CustomAttributes.Select(attribute => attribute.AttributeType)
where !attributeTypes.Any(type => type == typeof(NonSerializedAttribute) || type == typeof(ObsoleteAttribute))
where attributeTypes.Contains(typeof(SerializeReference))
|| (
(field.IsPublic || attributeTypes.Contains(typeof(SerializeField)))
&& (field.FieldType.IsSerializable || field.FieldType.IsSubclassOf(typeof(Object)))
)
select field;
// Copy our serialized values.
foreach (var finfo in finfos)
{
finfo.SetValue(component, finfo.GetValue(this));
}
};
}
}
#endif
}
This won’t override [MovedFrom()] !
This work if the interface data was a monobehaviour, but what if its simply data in a scriptableObject?