Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Generics serialization

Discussion in '2020.1 Beta' started by AlkisFortuneFish, Sep 17, 2019.

  1. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    • Scripting: The serializer can now serialize fields of generic types (e.g. MyClass someField) directly; it is no longer necessary to derive a concrete subclass from a generic type in order to serialize it.
    I'd argue this beauty needs its own entry in the 2020.1 features list! :)
     
    marcospgp, Neiist, zkyle1337 and 13 others like this.
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    That's neat!

    I'd love to know how this interacts with property drawers. How would we go about defining a custom property drawer for a generic class?
     
  3. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Code (CSharp):
    1.  
    2. [CustomPropertyDrawer(typeof(Container<>))]
    3. public class ContainerPropertyDrawer : PropertyDrawer
    4. {
    5.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    6.     {
    7.         EditorGUI.PropertyField(position, property.FindPropertyRelative("Value"), new GUIContent(fieldInfo.FieldType.GetGenericArguments()[0].FullName));
    8.     }
    9. }
    10.  
    11. [CustomPropertyDrawer(typeof(Container<string>))]
    12. public class StringContainerPropertyDrawer : PropertyDrawer
    13. {
    14.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    15.     {
    16.         EditorGUI.PropertyField(position, property.FindPropertyRelative("Value"), new GUIContent("String!"));
    17.     }
    18. }
    19.  
    This works. You can get the generic argument type with fieldInfo.FieldType.GetGenericArguments(). Plus it looks like it picks the more specific attribute if it exists.
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Cool, thanks. That'll be really, really useful. We have a SerializableDictionary implementation, and each of them requires both a concrete subclass and a concrete drawer subclass, so this will save us a ton of code.
     
    Benji_1996, phobos2077 and ModLunar like this.
  5. stopiccot_tds

    stopiccot_tds

    Joined:
    Oct 1, 2016
    Posts:
    111
    Best feature after nested prefabs
     
  6. Lhawika

    Lhawika

    Joined:
    May 27, 2015
    Posts:
    53
    What about generic SerializeReference ?

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable]
    5. public class A
    6. {
    7.     public int a;
    8. }
    9.  
    10. [Serializable]
    11. public class B : A
    12. {
    13.     public int b;
    14. }
    15.  
    16. [Serializable]
    17. public class C : A
    18. {
    19.     public int c;
    20. }
    21.  
    22. [Serializable]
    23. public class D<T> : A
    24. {
    25.     public T d;
    26. }
    27.  
    28. public class TestSerializeReference : MonoBehaviour
    29. {
    30.     [SerializeReference]
    31.     public A a = null;
    32.  
    33.     public D<int> d = new D<int>();
    34.  
    35.     public bool changeType = false;
    36.  
    37.     private void OnValidate()
    38.     {
    39.         if (changeType)
    40.         {
    41.             if (a == null || a is D<int>)
    42.             {
    43.                 a = new A();
    44.             }
    45.             else if (a is C)
    46.             {
    47.                 a = new D<int>();
    48.             }
    49.             else if (a is B)
    50.             {
    51.                 a = new C();
    52.             }
    53.             else
    54.             {
    55.                 a = new B();
    56.             }
    57.  
    58.             changeType = false;
    59.         }
    60.     }
    61. }
    62.  
    I took a quick look at it recently and only got an editor crash when executing "a = new D<int>();". Hope it's juste an alpha bug and that it will be supported in the future.
     
  7. Xarbrough

    Xarbrough

    Joined:
    Dec 11, 2014
    Posts:
    1,188
    This looks nice, tested a few cases. This will probably enable Unity to also mark the UnityEvent<T> classes as serializable and make them non abstract; so that we could serialize UnityEvent<bool> directly without creating a subclass first.
     
  8. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Yep, just found that too and reported it with a tiny repro. It crashes if any generic class is directly serialized with SerializeReference with compute_class_bitmap: Invalid type 13 for field Container`1[T]:Value

    @LeonhardP Case 1185214
     
    LeonhardP and Peter77 like this.
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    All editor crashes are bugs that should be reported. It might not be that the feature is supposed to be supported, but nothing should crash the editor.
     
  10. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    Are list of generic classes supposed to show up in the inspector?
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class TestSerialization : MonoBehaviour
    6. {
    7.     [System.Serializable]
    8.     public class D<T>
    9.     {
    10.         public T d;
    11.         public T d2;
    12.     }
    13.  
    14.     public List<D<int>> myclass = new List<D<int>>();// doesn't show up in the inspector'
    15.     public D<int> myclass2;
    16. }
    17.  
     
  11. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    There's an open bug about that right now. The main issue is fixed but we're resolving one secondary issue about how it interacts with [SerializeReference].
     
    quabug and laurentlavigne like this.
  12. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    LeonhardP likes this.
  13. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    Is there any plan to support generics for scriptable objects too?
    Knowing enough about how they work and comments about UnityEngine.Object not being supported together with SerializeReference this is probably a no.
     
  14. tsukimi

    tsukimi

    Joined:
    Dec 10, 2014
    Posts:
    80
    In the below example, genericMonoA showed in inspector as an Object field(as expect),
    but genericMonoB was treated as a pure class with foldout in inspector, and will call new() on game start (prohibited for MonoBehaviour)
    Is this some kind of bug?

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [System.Serializable]
    4. public class GenericMono<T> : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     T t;
    8. }
    9.  
    10. public class GenericMonoFloat : GenericMono<float> { }
    11.  
    12. public class GenericBehaviour : MonoBehaviour
    13. {
    14.     [SerializeField]
    15.     GenericMonoFloat genericMonoA;
    16.  
    17.     [SerializeField]
    18.     GenericMono<float> genericMonoB;
    19. }
    20.  
     
  15. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    Is there a way to serialise it manually and get the result? Similar to how JSONUtility works - it has the same constraints as the current serialisation used by Unity. Is there an equivalent for this new serialisation?
     
  16. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Yes, I think so - please file a bug report via the Editor so our QA team can process it properly.
     
  17. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    I'm not sure what you mean. This new behaviour is just an extension to the existing serialiser - it's not a whole new serialisation system.
     
  18. jehk27

    jehk27

    Joined:
    Mar 23, 2013
    Posts:
    16
    This feature doesn't seem to work with arrays/lists of types that have a generic parameter. For example...

    Code (CSharp):
    1. [CreateAssetMenu(menuName = "Tables/Weapon")]
    2. public class WeaponTable : Table<Weapon>
    3. {
    4. }
    5.  
    6. public abstract class Table<TValue> : ScriptableObject
    7. {
    8.     public Row<TValue>[] rows;
    9. }
    10.  
    11. [Serializable]
    12. public struct Row<TValue>
    13. {
    14.     public TValue value;
    15.  
    16.     public int rating;
    17. }
    18.  
    It works if table has just one row but not an array of them.
     
  19. tsukimi

    tsukimi

    Joined:
    Dec 10, 2014
    Posts:
    80
    bug report sent: 1199278
    Thank you for the reply! I always appreciate Unity's speedy supports
     
    superpig and Peter77 like this.
  20. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,293
    Yes we should be removing the abstract from UnityEvent in the future :)
     
  21. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    149
    Are there any plans to implement default serialization for common data structure types from System.Collections.Generic (specially Dictionaries)?
     
  22. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Other than List<T>, which is already implemented, we have no concrete plans to implement serialization for other types in System.Collections.Generic. (We know it'd be nice).
     
    marcospgp, lpiljek and karl_jones like this.
  23. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,618
    Can you shed some light why there are no concrete plans to implement serialization for other types in System.Collections.Generic? Is it technical difficulties or just other things are more important?
     
    marcospgp, phobos2077 and lpiljek like this.
  24. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Both. List<T> is easy to serialize because it's really just a wrapper around an array, but all the other types are more complicated, especially if we actually care about performance. I don't think it is impossible to solve - especially if we put some limitations on which generic parameters we allow - but it's not something that someone can just throw together in a weekend.
     
    marcospgp and AlejMC like this.
  25. TJHeuvel-net

    TJHeuvel-net

    Joined:
    Jul 31, 2012
    Posts:
    838
    Though by not implementing it many users will make their own variant, which might perform even worse. And it would make shipping plugins much easier if there is a default unity implementation.
     
  26. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    I don't think many people would care about anything else than Dictionary. I really can't see myself clamoring for a serializable HashSet or Stack.

    The big problem with the Dictionary is of course how to draw it. Serializing it is pretty trivial, although there might be some performance considerations I'm not seeing. But how do you make a view where it's easy to both change old relations and add new ones? How do you handle duplicate keys? etc.
     
    phobos2077 likes this.
  27. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,073
    It's cool. For perfection it would have to to serialize reference by interface.
    Consider this example: (IMonoBehaviour is fictional naturally)

    Code (CSharp):
    1.  
    2. interface IGetComponent:IMonoBehaviour{}
    3. interface ISetComponent:IMonoBehaviour{}
    4.  
    5. class GetSetComonent:MonoBehaviour,IGetComponent,ISetComponent{
    6. }
    7.  
    And then you could do in your code
    public ISetComponent setter;
    public IGetComponent getter;
     
    Last edited: Dec 6, 2019
  28. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    149
    Yeah, dictionaries are really ubiquitous when compared to other structures.

    Odin Inspector's views are pretty good
    https://twitter.com/devdogunity/status/857522495968219138?lang=en
     
    AlejMC and DoctorShinobi like this.
  29. DoctorShinobi

    DoctorShinobi

    Joined:
    Oct 5, 2012
    Posts:
    219
    I really wish Unity supported interface serialization. Not being able to drag and drop monos using their interface really complicates using interfaces in Unity.
     
  30. lpiljek

    lpiljek

    Joined:
    Nov 11, 2015
    Posts:
    12
    I just came here to see if you guys finally made dictionaries serialisable, guess not.
    All those problems have been solved a long, long time ago. Unity devs seem to be the only people on the planet still scratching their heads about it.
    And BTW serialising an associative array is something one can do in a weekend (been done before). Also, like someone said, performance of the solution integrated in the engine can't be worse than what we are all doing right now (i.e. hacking around it).

    Oh well, maybe Unity 2030 will do this right.
     
  31. tonycoculuzzi

    tonycoculuzzi

    Joined:
    Jun 2, 2011
    Posts:
    301
    Anyone know if this works for ScriptableObjects too?

    For example:
    Code (CSharp):
    1.  
    2. public class Container<T> : ScriptableObject
    3. {
    4.     //..
    5. }
    6.  
     
  32. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    For a ScriptableObject and any other native object to be created in general you need a concrete class to be derived, otherwise there is nowhere for the generic parameter to be resolved. As such, I cannot think of any particularly great reason to need to serialize generic UnityEngine.Object references, what is the use case?
     
    AlejMC likes this.
  33. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    References to generic MBs/SOs should work in a22 or later. That said, as @AlkisFortuneFish said, scenarios where you can use this are limited - e.g. I think you won't be able to persist such objects into assets.
     
    Peter77 likes this.
  34. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    If references to generic SOs/MBs work, what about referencing them via interface? This would make code so much neater!
     
    Peter77 likes this.
  35. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    Totally different problem. A generic SO/MB is still guaranteed to be an SO/MB once you go far enough up the type hierarchy; an interface could be anything, which means we cannot know ahead of time whether it should get serialised by reference or by value.
     
    AlkisFortuneFish likes this.
  36. M_R

    M_R

    Joined:
    Apr 15, 2015
    Posts:
    559
    add an attribute (or a flag on [SerializeReference]) that can be added to the field to let the dev specify that.

    e.g.
    Code (CSharp):
    1. [SerializeAsUnityObjectReference]
    2. public IFoo foo; // object field in the inspector, accepts both MB and SO implementing IFoo
    3.  
    4. [SerializeReference] //2019.3
    5. public IFoo bar = new Bar(); // serialized inside current SerializedObject, already implemented
     
  37. Prodigga

    Prodigga

    Joined:
    Apr 13, 2011
    Posts:
    1,123
    That's not a good solution because the point is that we would want to serialise references to both unity objects and POCOs within the same field across different instances of the object.
     
  38. stonstad

    stonstad

    Joined:
    Jan 19, 2018
    Posts:
    659
    The team’s contributions to Unity are much appreciated. JSON serialization is important.

    Although serialization to disk is an important use case, the primary use for JSON serialization is network transmission. With this in mind the Unity serializer is not all that useful. Compared to Newtonsoft.Json or System.Text.Json (Microsoft), the Unity serializer is not portable to servers. It doesn’t support dictionaries and it has a very light feature set by comparison.

    I think the existing Unity serializer should be retired in favor of System.Text.Json.

    System.Text.Json does not allocate garbage and it supports interfaces, generics, converters, and truly unrivaled performance. Did I mention no allocation and low memory footprint? Everyone wins by supporting Microsoft’s portable open source solution.
     
    Last edited: Feb 2, 2020
  39. swedishfisk

    swedishfisk

    Joined:
    Oct 14, 2016
    Posts:
    57
    So I've been playing around with the generic serialization over the weekend and I like it alot so far.

    Currently I'm working on a generic PropertyDrawer for a List containing a struct<T>. I've managed to get the generic type argument for the struct using reflection, even creating an instance of a generic List matching the generic parameter (using System.Activator.CreateInstance).

    But... I can't seem to get the actual values from the List in a clean way. I can access their SerializedProperty, but do I need need to parse it like so:

    Code (CSharp):
    1. // note: list variable is the serializedProperty I want to get the value from
    2. switch (list.type)
    3. {
    4.     case "string":
    5.     {
    6.         var val = list.stringValue;
    7.         break;
    8.     }
    9.     case "float":
    10.     {
    11.         var val = list.floatValue;
    12.         break;
    13.     }
    14.     // etc.
    15. }
    Because this does work but is very cumbersome seen as the amount of switch-cases becomes very large. My question is if I can somehow use the FieldInfo.GetValue(Obj) that can actually return the value regardless of object/primitive without so much boilerplate? If not could you consider implementing this? Some older forums posts also wish for this feature, but I couldn't get their workarounds to work properly thus I implemented this parsing switch.
     
    Last edited: Feb 10, 2020
  40. swedishfisk

    swedishfisk

    Joined:
    Oct 14, 2016
    Posts:
    57
    Turns out I didn't need that much reflection to get my project working anyway. Still the FieldInfo.GetValue would kick ass.

    Anyways here is an open source Generic Serializable Dictionary with a native look and feel in 66 LOC + property drawer: https://github.com/upscalebaby/generic-serializable-dictionary
     
    laurentlavigne likes this.
  41. jasonatkaruna

    jasonatkaruna

    Joined:
    Feb 26, 2019
    Posts:
    64
    Any news on the above? I can't seem to find it in the issue tracker. This sort of thing will allow us to dynamically serialize any object on a field with a wrapper generic:

    Code (CSharp):
    1. [SerializeReference]
    2. public object anyObject = 32; // this does not serialize
    3.  
    4. // ...
    5.  
    6. public interface IObject {}
    7. public class Wrapper<T> : IObject
    8. {
    9.     public T Value;
    10.  
    11.     public Wrapper(T value)
    12.     {
    13.         Value = value;
    14.     }
    15. }
    16.  
    17. [SerializeReference]
    18. public IObject anyObject = Wrapper(32); // this could serialize if it didn't crash the editor.
     
  42. OndrejP

    OndrejP

    Joined:
    Jul 19, 2017
    Posts:
    304
    We had a similar issue, we worked around that with this:
    Code (CSharp):
    1. [RefType(typeof(ISomething))]
    2. Object m_something;
    3.  
    4. ISomething Something => m_something as ISomething;
    Magic is done in property drawer for [RefTypeAttribute]:
    - allows dragging only interfaces in
    - when circle icon is clicked, object picker is opened with predefined search like this:
    t:Something t:OtherSomethingImplementation t:SomethingElse
    (all implementations of the interface on Object which can be found)

    Not ideal, but works. You can drag both MonoBehaviours and ScriptableObjects into the field.
    Serializing non-Object classes/structs was never the point of this (at least for us).

    Boilerplate-free solution, which @M_R suggests would be really nice: [SerializeAsUnityObjectReference]
    Although I'd prefer shorter name [SerializeObjectReference]
     
  43. Flying_Banana

    Flying_Banana

    Joined:
    Jun 19, 2019
    Posts:
    29
    I create ScriptableObjects in the editor scripts to support Undo/Redo for custom generic data that has no better containers. This doesn't seem to be working in 2020.1.0b5:

    Code (CSharp):
    1. // Data.cs
    2. public class Data<T> : ScriptableObject { }
    3.  
    4. // Test.cs
    5. [ExecuteInEditMode]
    6. public class Test : MonoBehavior {
    7.   void Update() {
    8.     // This returns null.
    9.     Debug.Log(ScriptableObject.CreateInstance<Data<string>>());
    10.   }
    11. }
     
    Last edited: Apr 20, 2020
  44. billykater

    billykater

    Joined:
    Mar 12, 2011
    Posts:
    329
    @Flying_Banana from all my experimentation (which was some time ago) you still need to have a non generic type to use with CreateInstance. You can just reference them in a generic type field.
     
  45. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I think the scenarios where this would be very useful is if you have a class
    Class FloatContainer:Container<float>{ }
    and another
    Class OtherFloatContainer:Container<float>{ }
    an then, in a Monobehaviour you could pass both types to a serialized reference like
    public Container<float> container;
    .
     
    Last edited: Apr 26, 2020
    AlejMC and phobos2077 like this.
  46. Nelson-William

    Nelson-William

    Joined:
    Aug 14, 2011
    Posts:
    2
    The Script Serialization documentation should be updated with the new SerializeReference and support for generic fields.
     
    marcospgp, AlejMC and swedishfisk like this.
  47. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,278
    Question: I use serialized generics a LOT, but of course using the old way of a class "wrapper" for each generic type.

    If I want to start using serialized generics, I'd lose all of my serialized data right? Any way to avoid this?
     
  48. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Maybe, maybe not, I have't seen what the serialized data actually looks like. Try it on a class and see what happens.

    To be honest, I can't imagine it would lose the data.
     
  49. AlkisFortuneFish

    AlkisFortuneFish

    Joined:
    Apr 26, 2013
    Posts:
    973
    Yep, as I thought, no difference in serialization:

    Code (CSharp):
    1. public class SomeScript : MonoBehaviour
    2. {
    3.     public SomeClass<float> Parametrised;
    4.     public FloatClass Derived;
    5. }
    6.  
    7. [Serializable]
    8. public class SomeClass<T>
    9. {
    10.     public T Value;
    11. }
    12.  
    13. [Serializable]
    14. public class FloatClass : SomeClass<float> { }
    Code (Yaml):
    1. --- !u!114 &963194229
    2. MonoBehaviour:
    3.   m_ObjectHideFlags: 0
    4.   m_CorrespondingSourceObject: {fileID: 0}
    5.   m_PrefabInstance: {fileID: 0}
    6.   m_PrefabAsset: {fileID: 0}
    7.   m_GameObject: {fileID: 963194225}
    8.   m_Enabled: 1
    9.   m_EditorHideFlags: 0
    10.   m_Script: {fileID: 11500000, guid: 316cfc69e884d9446b42dcbb702cb2dc, type: 3}
    11.   m_Name:
    12.   m_EditorClassIdentifier:
    13.   Parametrised:
    14.     Value: 10
    15.   Derived:
    16.     Value: 20
    17.  
     
  50. joshcamas

    joshcamas

    Joined:
    Jun 16, 2017
    Posts:
    1,278
    HUZZAH
     
    Awarisu likes this.