Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Something like Serializable that allows null?

Discussion in 'Scripting' started by scone, Apr 25, 2012.

  1. scone

    scone

    Joined:
    May 21, 2008
    Posts:
    244
    Howdy,

    I came across a bit of an issue when trying to take advantage of the [System.Serializable] attribute added to a non-Monobehaviour class. I love the fact that it enables you to modify the parameters of one of those objects in the inspector whenever it's a public variable, but it seems as though it also makes it so that that variable starts out instantiated no matter what.

    I actually haven't tried setting it to null in Start(), but that's obviously a band-aid. Is there any way to get similar functionality as well as the ability to optionally specify whether the object exists? That is, is there any way to do this without writing a custom editor script?

    I'm guessing the answer is "no" so maybe I should submit a feature request?
     
  2. scone

    scone

    Joined:
    May 21, 2008
    Posts:
    244
    Actually, there seem to be a couple of other very rigid constraints on Serializable classes. For example, I found that the editor did not expose a field of the same type. That is, if I have a [System.Serializable] Foo, any field bar of type Foo won't show up in the inspector. This makes me a sad panda :(
     
  3. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    429
    What type of variable are you trying to serialize? A class? Just make it inherit UnityEngine.Object like this:

    Code (csharp):
    1.  
    2. [Serializable]
    3. public class TestClass : UnityEngine.Object
    4. {
    5.     public Vector3 yoyo;
    6. }
    7. public TestClass test;
    That will allow you to set it to something at runtime, and check if it's null.
     
  4. scone

    scone

    Joined:
    May 21, 2008
    Posts:
    244
    Wow, thanks for the help! I hadn't thought of that. BUT...

    Now that the class is a UnityEngine.Object, I can't create a new one and set values on it in the inspector, which is one of the primary reasons why I wanted it to be serializable.

    Any ideas?
     
  5. scone

    scone

    Joined:
    May 21, 2008
    Posts:
    244
    Any ideas here? I'm still hoping to find a way to create inspector-editable fields that can be null. I can't even seem to do it with a custom Inspector, since the objects are auto-instantiated whenever you view it, even if you don't draw the default inspector.
     
  6. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    429
    This can be done quite easily with a custom inspector and a nullable modifier on any class (?). So your class looks something like this:

    Code (csharp):
    1. public class TestClass2 : MonoBehaviour
    2. {
    3.     public Vector3? Vector;
    4. }
    And your inspector class in your Editor folder looks like this:

    Code (csharp):
    1. [CustomEditor(typeof(TestClass2))]
    2. public class TestClass2Inspector : Editor
    3. {
    4.     public override void OnInspectorGUI()
    5.     {
    6.         TestClass2 t = target as TestClass2;
    7.  
    8.         if (t.Vector.HasValue)
    9.         {
    10.             t.Vector = EditorGUILayout.Vector3Field("Vector", t.Vector.Value);
    11.             if (GUILayout.Button("Set Null"))
    12.                 t.Vector = null;
    13.         }
    14.         else
    15.             if (GUILayout.Button("Create Vector"))
    16.                 t.Vector = new Vector3();
    17.  
    18.         if (GUI.changed)
    19.             EditorUtility.SetDirty(target);
    20.     }
    21. }
    Hope this helps!
     
    ZabAyAI likes this.
  7. scone

    scone

    Joined:
    May 21, 2008
    Posts:
    244
    Ooh, this is interesting, and I've never seen this ? operator before....

    I'll give it a try soon. I was able to work around the issue using arrays and a custom inspector. Basically if I just needed one serializable object that could be set to null, I made an array and if it was size 0 then that was the same as null. I'll let you know if this works for me.
     
  8. Bubsavvy

    Bubsavvy

    Joined:
    Sep 18, 2017
    Posts:
    48
    Unity absolutely should NOT be treating classes as structs if they are serializable in my honest opinion. Doing so forces developers to figure out strange anti-pattern ways of solving issues caused by it via null checking. It also ignores basic truths surrounding what the default C# behavior demands AFAIK in terms of the nullable nature of classes. I understand that they have their reasons for doing so, but it is causing us to have to do unreliable strategies such as converting Serialized collections i.e (Arrays) of custom types to structs and checking for default() or keeping it as a class and creating a new property that denotes an index as "null". We should be given a choice at least to mark any structure that is Serialized, primarily classes, to not auto populate null indexes or null references with the default properties defined by the class for that specific Serializable instance.

    Code (CSharp):
    1. [System.Serializable]
    2.  
    3.     // Values are populated upon deserialization... Thanks...
    4.     public class ItemBundleProperties
    5.     {
    6.         public string tooltipTitle;
    7.         public string description;
    8.         public string type;
    9.         public string slug;
    10.         public int value;
    11.         public int power;
    12.         public int defense;
    13.         public int vitality;
    14.         public int heal;
    15.         public int rarity;
    16.         public int itemCount;
    17.         public int capacity;
    18.         public bool isStackable;
    19.     }
    20.     // Now when I try something like this it will fail...
    21.     public void addToInventory(ItemBundleProperies itemBundle){
    22.         // FAIL... just kidding you can't be null nextAvailableslot because although I am an array of objects the type class
    23.         // Unity manhandled me during the serialization process :C
    24.         if(inventory[nextAvailableslot] == null){
    25.             inventory[nextAvailableslot] = itemBundle
    26.         }
    27.     }
    28.  
    I know nothing will be done about this so... Hello useless extra property "bool slotFilled" :mad:
     
    Last edited: Apr 21, 2019
    a436t4ataf likes this.
  9. maltakereuz

    maltakereuz

    Joined:
    Mar 29, 2015
    Posts:
    52
    Oh... i am making inventory for my RPG game. So i ended up with [Serializable] MyItem and Player with inventory as List<MyItem> and some slots like:

    Code (CSharp):
    1. MyItem helm;
    2. MyItem armor;
    3. MyItem leftWeapon;
    4. ... etc. ...
    So this will not really work in Unity, as i can not just set leftWeapon to NULL to remove item from player slot? Is any solution better then bool slotFilled? It is pertty easy to oversee ene error-prone.

    P.S. Well I found the solution for my case. It is using payed asset Odin, wich comes with built-in serialized system. It is doubtfull to use an xxl-asset only to allow null-refs in Inspector, but i use this asset for many other reasons. Just to derive from SerializedMonoBehavior and mark all fields [NonSerialized, OdinSerialize] is enough. But I realize this is no an options for many devs.
     
    Last edited: Oct 4, 2020
  10. Dextozz

    Dextozz

    Joined:
    Apr 8, 2018
    Posts:
    488
    If you want to avoid paying a ton of money for a glorified inspector, you can use Scriptable Objects. They allow for null values, although, some other issues come with them, nothing unsolvable.

    Just have your class' data derive from a scriptable object, create it in the project, and drag and drop it in the inspector if needed. That allows you to either have it as null by default or actually fill it with some variables.
     
  11. deeprest

    deeprest

    Joined:
    Jun 8, 2016
    Posts:
    17
    This is a response to the original post, which is still a relevant question.

    Make sure to use the [SerializeReference] attribute instead of [SerializeField]. If "data" below was declared using [SerializeField], Unity will give the variable a value even if it is null, in order to show it in the inspector.
    With [SerializeReference] if the reference is null nothing but the name is shown in the inspector.

    Code (CSharp):
    1. [Serializable]
    2. public class Data
    3. {
    4.   public int whatever;
    5. }
    6.  
    7. public class MonoBehaviourOrScriptableObject : ScriptableObject
    8. {
    9.   [SerializeReference] Data data;
    10. }
     
    Last edited: Oct 25, 2020
    Zebadiah, Jackbot92, alfish and 6 others like this.
  12. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    oh boy, this thread will be hijacked each year for an eternity :)
    IT'S 8 YEARS OLD!
     
  13. deeprest

    deeprest

    Joined:
    Jun 8, 2016
    Posts:
    17
    Funny... but who cares if it's old? This 8 year old post is still a valid question. Hopefully someone will find my post and same themselves some time- the time it takes to find something you didn't know existed.
     
    Last edited: Apr 11, 2021
    AShenawy and StacyKit10 like this.
  14. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    No, the replies are valid, just saying. Sometimes it's just silly, in this case, the updates are relevant, especially since SerializeReference
     
    Bunny83 likes this.
  15. ENDAS_ORIGINAL

    ENDAS_ORIGINAL

    Joined:
    Apr 26, 2018
    Posts:
    2
    here is my workaround:
    Code (CSharp):
    1. [System.Serializable]
    2. public class Data
    3. {
    4.  public Item item;
    5. }
    Code (CSharp):
    1. [SerializeField]
    2. private Data m_myData; //visible in inspector but not visible for other classes
    3. [HideInInspector]
    4. public Data myData //vice versa
    5. {
    6.  get
    7.  {
    8.   if (m_myData.item != null)
    9.   {
    10.    return myData;
    11.   }
    12.   else
    13.   {
    14.    return null;
    15.   }
    16.  }
    17. }
     
    Last edited: Jan 4, 2021
    SimDevs likes this.
  16. herrmutig

    herrmutig

    Joined:
    Nov 30, 2020
    Posts:
    22
    I foundthis works very well:


    Code (CSharp):
    1. [System.Serializable]
    2. public class SerializableData
    3. {
    4.   // your properties
    5. }
    6.  
    7. public class foo : Monobehaviour
    8. {
    9.     [SerializeReference] private SerializableData data;
    10.  
    11.     void Update()
    12.    {
    13.         Debug.Log(data == null) ; // returns true.
    14.  
    15.    }
    16.  
    17.  
    18. }
     
  17. jdoonan61

    jdoonan61

    Joined:
    Jun 7, 2015
    Posts:
    13
    The `[HideInInspectorAttribute] is unnecessary for properties as Unity doesn't serialize them anyways.
     
  18. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    also who listens to Iron Maiden any more? xD
     
  19. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,543
    Well, this was an unnecessary bump ^^. If you want to point out something about that "workaround", then you should state that it actually doesn't work as his read-only property currently has an infinite recursion on itself and would cause a stack overflow exception.
    The issue is that
    return myData;
    should be
    return m_myData;


    Maybe if you're a "teenage dirtbag" ;)
     
    orionsyndrome likes this.
  20. AnOrdinarySandwich

    AnOrdinarySandwich

    Joined:
    Feb 9, 2017
    Posts:
    9
    Since this old thread still has some new content, I thought I'd offer another option for having a serialized class having no value. The answer: an array.

    Create an array as a class member of the to-serialize class in question. Then, the inspector will only initialize the array to a zero length. Thus, no "meat" has been created for the object since it doesn't have a valid index. The code can always only use the first index (zero) of the array as its value source and can define it as needed.

    While yes it is a work-around, its quite a simple method to incorporate, doesn't require the awkward (IMHO) ? operator, does exactly what the original poster wanted, and it doesn't require a custom inspector to set/check for nulls and the like.

    Just my two cents :)
     
  21. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,127
    A struct similar to Nullable<T> could be used for modeling an optional value.
    Code (CSharp):
    1. [Serializable]
    2. public struct Optional<T>
    3. {
    4.     [SerializeField]
    5.     private bool hasValue;
    6.    
    7.     [SerializeField]
    8.     private T value;
    9.  
    10.     public Optional(T value)
    11.     {
    12.         hasValue = value != null;
    13.         this.value = value;
    14.     }
    15.  
    16.     public bool HasValue => hasValue && value != null;
    17.  
    18.     public T Value
    19.     {
    20.         get => hasValue ? value : default;
    21.        
    22.         set
    23.         {
    24.             hasValue = value != null;
    25.             this.value = value;
    26.         }
    27.     }
    28.  
    29.     public bool TryGetValue(out T value)
    30.     {
    31.         if(hasValue)
    32.         {
    33.             value = this.value;
    34.             return value != null;
    35.         }
    36.  
    37.         value = default;
    38.         return false;
    39.     }
    40.  
    41.     public static implicit operator T(Optional<T> optional) => optional.Value;
    42.     public static explicit operator Optional<T>(T value) => new Optional<T>(value);
    43. }
    Example usage:
    Code (CSharp):
    1. [Serializable]
    2. public class Character
    3. {
    4.     [SerializeField]
    5.     private string firstName;
    6.  
    7.     [SerializeField]
    8.     private Optional<string> lastName;
    9.  
    10.     public string FirstName => firstName;
    11.  
    12.     public string FullName => lastName.HasValue ? firstName + " " + lastName : firstName;
    13.  
    14.     public bool TryGetLastName(out string lastName)
    15.     {
    16.         if(this.lastName.HasValue)
    17.         {
    18.             lastName = this.lastName;
    19.             return true;
    20.         }
    21.  
    22.         lastName = null;
    23.         return true;
    24.     }
    25. }
     
    orionsyndrome and Bunny83 like this.
  22. jdoonan61

    jdoonan61

    Joined:
    Jun 7, 2015
    Posts:
    13
    I'll admit I missed the lack of `m_` in front of the return value, but I'll also add that there is no purpose in the if else statement, if you simply return the field, if it's null it'll return null.

    EDIT
    After looking at the code again, I also missed the if .item is not null part of the if/else
     
  23. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,543
    That's actually not completely true. Even if you just do a null check, explicitly returning null could have a benefit. If the object is "fake null" then the if check will fail as the object pretends to be null. Though fake null objects are still objects and the alive check has some overhead. By actually returning null you would break the chain of passing a dead object around.

    Of course that would not apply in this case here since the object tested and the object returned is not the same. Like you mentioned in your edit, the if statement is kinda dangerous since "m_myData" could also be null. So the getter should look like this:

    Code (CSharp):
    1. public Data myData //vice versa
    2. {
    3.     get
    4.     {
    5.         if (m_myData == null || m_myData.item == null)
    6.             return null;
    7.         return m_myData;
    8.     }
    9. }
    This would be safe.
     
    orionsyndrome likes this.