Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
2
Question by The True Zap · Apr 05, 2015 at 02:06 PM · serializationscriptableobjectgenericsserializablesubclass

Serializing specialized subclasses of generic classes not working

Hello!

I am working on an editor tool and in the process I stumbled upon the following problem with Unity's data serialization:

If we have a custom generic class like

 public class CustomGenericClass<T> {...}

and a subclass

 [System.Serializable]
 public class SpecializedCustomClass : CustomGenericClass<int> {...}

then SpecializedCustomClass will NOT serialize! Contrary to the answer here for example.

Of course, I prepared an example to demonstrate the faulty behaviour. Let's start with a working example without generics:

SerializableDictionary is a Unity-serializable version of a dictionary:

 using UnityEngine;
 using System;
 using System.Collections.Generic;
 
 public class SerializableDictionary
 {
   [Serializable]
   public struct SerializableKeyValuePair
   {
     public int key;
     public int value;
   }
 
   public Dictionary<int, int> dic;
   public SerializableKeyValuePair[] pairs;
 
   public SerializableDictionary()
   {
     dic = new Dictionary<int, int>();
     pairs = new SerializableKeyValuePair[0];
   }
 
   public void Serialize()
   {
     Array.Resize<SerializableKeyValuePair>(ref pairs, dic.Count);
 
     int i = 0;
     foreach (KeyValuePair<int, int> kv in dic) {
       pairs[i].key = kv.Key;
       pairs[i].value = kv.Value;
       ++i;
     }
   }
 
   public void Deserialize()
   {
     dic.Clear();
 
     foreach (SerializableKeyValuePair pair in pairs)
       dic.Add(pair.key, pair.value);
   }
 }

We also have a ScriptableObject-derived class using the dictionary:

 using UnityEngine;
 using System;
 using System.Collections.Generic;
 
 public class SomeWindowData : ScriptableObject, ISerializationCallbackReceiver
 {
   public SerializableDictionary someSerializedDic;
 
   public SomeWindowData()
   {
     someSerializedDic = new IntToIntDictionary();
   }
 
   public void OnBeforeSerialize()
   {
     someSerializedDic.Serialize();
   }
 
   public void OnAfterDeserialize()
   {
     someSerializedDic.Deserialize();
   }
 }

In this first case, SomeWindowData uses a public SerializableDictionary variable.

Then, we finally have a window class using SomeWindowData, to test the serialization by displaying the dictionary contents:

 using UnityEditor;
 using UnityEngine;
 using System.Collections.Generic;
 
 public class SomeWindow : EditorWindow
 {
   private const string DefaultAssetPath = "Assets/somewindowdata.asset";
 
   private SomeWindowData windowData;
 
   public static SomeWindowData CreateSomeWindowDataAssetAtPath(string path)
   {
     SomeWindowData windowData = ScriptableObject.CreateInstance<SomeWindowData>();
     AssetDatabase.CreateAsset(windowData, path);
     AssetDatabase.SaveAssets();
     AssetDatabase.Refresh();
 
     return windowData;
   }
 
   [MenuItem("Window/Some ****** Up Window")]
   static void ShowWindow()
   {
     EditorWindow.GetWindow(typeof(SomeWindow), false, "****");
   }
 
   void OnEnable()
   {
     windowData = AssetDatabase.LoadAssetAtPath(DefaultAssetPath, typeof(SomeWindowData)) as SomeWindowData;
     if (!windowData) {
       windowData = CreateSomeWindowDataAssetAtPath(DefaultAssetPath);
       generateKeys(10);
     }
   }
 
   void OnGUI()
   {
     foreach (KeyValuePair<int, int> kv in windowData.someSerializedDic.dic) {
       EditorGUILayout.BeginHorizontal();
       EditorGUILayout.IntField(kv.Key);
       int newValue = EditorGUILayout.IntField(kv.Value);
       EditorGUILayout.EndHorizontal();
 
       if (newValue != kv.Value) {
         windowData.someSerializedDic.dic[kv.Key] = newValue;
         EditorUtility.SetDirty(windowData);
         break;
       }
     }
   }
 
   public void generateKeys(int n)
   {
     if (windowData.someSerializedDic.dic == null)
       windowData.someSerializedDic.dic = new Dictionary<int, int>();
 
     windowData.someSerializedDic.dic.Clear();
 
     for (int i = 1; i <= n; ++i)
       windowData.someSerializedDic.dic.Add(i, i*i);
 
     EditorUtility.SetDirty(windowData);
   }
 }


The window displays the key-value pairs and allows you to change the value entries (not the keys). It also has a generator function, which creates n keys for the dictionary. This example code is solely for testing the serialization.

To test the code, create a new project, put above three classes in separate .cs files and inside an Editor folder and you should see a new Window menu entry. Clicking on it, an asset named "Assets/somewindowdata.asset" is created and the following window should appear:

alt text

You can alter the values (right column) and entering/exiting play mode and/or restarting the Unity editor will save the values and you should see the same window again. So - serialization is working!

Now we move to subclasses of generic classes. As a transition, we slightly alter the class hierarchy to prepare for the next case and confirm that the serialization of subclasses works correctly.

We add following class:

 [System.Serializable]
 public class IntToIntDictionary : SerializableDictionary {}

and we adjust the SomeWindowData class to use the IntToIntDictionary subclass:

using UnityEngine; using System; using System.Collections.Generic;

 public class SomeWindowData : ScriptableObject, ISerializationCallbackReceiver
 {
   public IntToIntDictionary someSerializedDic;
 
   public SomeWindowData()
   {
     someSerializedDic = new IntToIntDictionary();
   }
 
   public void OnBeforeSerialize()
   {
     someSerializedDic.Serialize();
   }
 
   public void OnAfterDeserialize()
   {
     someSerializedDic.Deserialize();
   }
 }

We delete the somewindowdata.asset file and open our window again. The asset file gets created again, We set some values, restart Unity and all is well. Again, serialization working as expected.

Now it is time to move to the generic base class. We make the SerializableDictionary class generic with Key and Value type parameters:

 using UnityEngine;
 using System;
 using System.Collections.Generic;
 
 public class SerializableDictionary<T, U>
 {
   [Serializable]
   public struct SerializableKeyValuePair
   {
     public T key;
     public U value;
   }
 
   public Dictionary<T, U> dic;
   public SerializableKeyValuePair[] pairs;
 
   public SerializableDictionary()
   {
     dic = new Dictionary<T, U>();
     pairs = new SerializableKeyValuePair[0];
   }
 
   public void Serialize()
   {
     Array.Resize<SerializableKeyValuePair>(ref pairs, dic.Count);
 
     int i = 0;
     foreach (KeyValuePair<T, U> kv in dic) {
       pairs[i].key = kv.Key;
       pairs[i].value = kv.Value;
       ++i;
     }
   }
 
   public void Deserialize()
   {
     dic.Clear();
 
     foreach (SerializableKeyValuePair pair in pairs)
       dic.Add(pair.key, pair.value);
   }
 }

We adjust our derived class:

 [System.Serializable]
 public class IntToIntDictionary : SerializableDictionary<int, int> {}

The type of our SomeWindowData member is already set to IntToIntDictionary, so we should be ready to go. Again, we delete the somewindowdata.asset file and open another window. We modify some values, restart Unity and AAAAAAARGGHHH!!! We see an empty window appearing, meaning our data was not stored - serialization is broken!!!

alt text

Now my question is "WHY"?

A few important notes:

- [Serializable] attribute

+++ In the first case, it is enough to put the attribute on the SeralizedDictionary class for serialization to work.

+++ In the second case, you can omit the attribute on the SerializedDictionary and only put it on the IntToIntDictionary subclass for serialization to work.

+++ In the third example, even putting the attribute on all classes didn't make the magic happen...

- Number of generic type parameters

+++ Just in case, I tried with only one type parameter, creating a dictionary instead of . The result is the same - serialization does not work!

- But it should work!

+++ Again, according to this post it should work! For example, according to the answers of the gentlemen "andrey_bryzgalov" (top answer, 14 up-votes) and "Immanuel Scholz" this should be the way to do it and it should work without problems. Yet, this is not the case!

+++ Let's assume the above would work, would it have been so hard for Unity's programmers to make some preprocessing and to create the empty subclasses on the fly before compiling? A small and simple step, but it would allow you to serialize custom generic classes!

+++ Again, if this would actually not fail, I could put the subclass definition inside the SomeWindowData class as a nested class and omit the creation of a two-line source file.

So why is this not working? Am I doing something wrong? Did this work at some point in time with some versions of Unity, but is broken now? Also, in given link above, there were a few people complaining that it doesn't work, but either no one answered this or it was nothing constructive. So is this being effectively ignored by Unity Team?

For now, I will just duplicate the SerializableDictionary code for each specialization, at least at that level it works. Unity's crappy and unstable serialization really humbles you and you are even happy with some working fragments :)

So anyway, I would love to hear some opinions, suggestions and hopefully solutions of the described problem. Thanks in advance, everyone! And sorry for the long post :)

For information, the code was tested (and failure confirmed) on Unity v4.6.3f1 and v5.0.1f1.

window1.jpg (18.4 kB)
window2.jpg (7.5 kB)
Comment
Add comment · Show 1
10 |3000 characters needed characters left characters exceeded
â–¼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image XGT08 · Mar 11, 2016 at 02:51 PM 0
Share

I know this is an old thread. I've just ran into the same issue and I don't know how to solve it. Have you managed to find a solution?

0 Replies

· Add your reply
  • Sort: 

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

3 People are following this question.

avatar image avatar image avatar image

Related Questions

How to serialize a concrete derived class from generic ScriptableObject base 1 Answer

Make the same serialized class as a ScriptableObject? 1 Answer

How to trigger actual field renaming In embedded instances of ScriptableObject asset files while using [FormerlySerializedAs] attribute? 1 Answer

2 problems with serialization of a generic list of a custom class 3 Answers

ScriptableObject stored in MonoBehaviour lost on quit 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges