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. Dismiss Notice

Cannot serialize a GUID field in class?

Discussion in 'Scripting' started by skalev, Oct 31, 2012.

  1. skalev

    skalev

    Joined:
    Feb 16, 2012
    Posts:
    264
    Hi All.

    I have a MonoBehaviour that has two GUID fields in it. I set those through the custom inspector, but for some reason, it doesn't store the changes into the component, so when I hit play or save the scene the values disappear.

    I'm setting the target to be dirty in my custom inspector, and I have also tried to mark the GUIDs with [SerializeField].

    Any one has any insight on this?
     
  2. Landern

    Landern

    Joined:
    Dec 21, 2008
    Posts:
    354
    Can you get away with using a string to represent?

    Code (csharp):
    1.  
    2. string guid = Guid.NewGuid();
    3.  
    May not be ideal, just a thought, structure the var however(in Start(), some method, etc).
     
  3. skalev

    skalev

    Joined:
    Feb 16, 2012
    Posts:
    264
    Yeah, That is what i've done, however the var is linked to other information, so it isn't really that ideal. I can probably get away with it, but i don't understand WHY this is happening, which is the more frustrating part. It's not like GUID is a complex class...
     
    Last edited: Oct 31, 2012
  4. Demigiant

    Demigiant

    Joined:
    Jan 27, 2011
    Posts:
    3,239
    Unity has a limited range of types that can be serialized (here is the list), and there's no easy workaround for that. You can still serialize a custom class though, with all the additional data you need, as long as the serialized fields are of one of those types.
     
    drussilla likes this.
  5. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,081
    I'm use this solution

    using System;
    using System.Runtime.InteropServices;
    using UnityEngine;

    [StructLayout( LayoutKind.Explicit ), Serializable]
    public class UnityGuid: IComparable, IComparable<Guid>, IEquatable<Guid>
    {
    public UnityGuid( )
    {
    Guid = Guid.NewGuid( );
    }

    [FieldOffset(0)]
    public Guid Guid;
    [FieldOffset(0), SerializeField]
    private Int32 GuidPart1;
    [FieldOffset(4), SerializeField]
    private Int32 GuidPart2;
    [FieldOffset(8), SerializeField]
    private Int32 GuidPart3;
    [FieldOffset(12), SerializeField]
    private Int32 GuidPart4;

    public static implicit operator Guid ( UnityGuid uGuid )
    {
    return uGuid.Guid;
    }

    public Int32 CompareTo ( object obj )
    {
    if( obj == null )
    return -1;

    if( obj is UnityGuid )
    return ((UnityGuid)obj).Guid.CompareTo( Guid );

    if( obj is Guid )
    return ((Guid)obj).CompareTo( Guid );

    return -1;
    }
    public Int32 CompareTo ( Guid other )
    {
    return Guid.CompareTo( other );
    }
    public Boolean Equals ( Guid other )
    {
    return Guid == other;
    }

    public override Boolean Equals ( object obj )
    {
    if( obj == null )
    return false;

    if( obj is UnityGuid )
    return (UnityGuid)obj == Guid;

    if( obj is Guid )
    return (Guid)obj == Guid;

    return false;
    }
    public override Int32 GetHashCode ( )
    {
    return Guid.GetHashCode( );
    }
    }
     
    FM-Productions likes this.
  6. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,081
    I'm use this Solution

    using System;
    using System.Runtime.InteropServices;
    using UnityEngine;

    [StructLayout( LayoutKind.Explicit ), Serializable]
    public class UnityGuid: IComparable, IComparable<Guid>, IEquatable<Guid>
    {
    public UnityGuid( )
    {
    Guid = Guid.NewGuid( );
    }

    [FieldOffset(0)]
    public Guid Guid;
    [FieldOffset(0), SerializeField]
    private Int32 GuidPart1;
    [FieldOffset(4), SerializeField]
    private Int32 GuidPart2;
    [FieldOffset(8), SerializeField]
    private Int32 GuidPart3;
    [FieldOffset(12), SerializeField]
    private Int32 GuidPart4;

    public static implicit operator Guid ( UnityGuid uGuid )
    {
    return uGuid.Guid;
    }

    public Int32 CompareTo ( object obj )
    {
    if( obj == null )
    return -1;

    if( obj is UnityGuid )
    return ((UnityGuid)obj).Guid.CompareTo( Guid );

    if( obj is Guid )
    return ((Guid)obj).CompareTo( Guid );

    return -1;
    }
    public Int32 CompareTo ( Guid other )
    {
    return Guid.CompareTo( other );
    }
    public Boolean Equals ( Guid other )
    {
    return Guid == other;
    }

    public override Boolean Equals ( object obj )
    {
    if( obj == null )
    return false;

    if( obj is UnityGuid )
    return (UnityGuid)obj == Guid;

    if( obj is Guid )
    return (Guid)obj == Guid;

    return false;
    }
    public override Int32 GetHashCode ( )
    {
    return Guid.GetHashCode( );
    }
    }
     
    aegis123321 likes this.
  7. drussilla

    drussilla

    Joined:
    Mar 20, 2014
    Posts:
    9
    Wandersoul likes this.
  8. SearousK

    SearousK

    Joined:
    Mar 21, 2020
    Posts:
    6
    Sorry for necroing this, but if anyone's looking for a solution and doesn't want to take the time to write their own wrapper, I've been using this one that I wrote. It includes a property drawer as well. I haven't tested it a ton, but it's simple enough it should work everywhere.

    SerializableGuid class:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. /// <summary>
    5. /// Serializable wrapper for System.Guid.
    6. /// Can be implicitly converted to/from System.Guid.
    7. ///
    8. /// Author: Searous
    9. /// </summary>
    10. [Serializable]
    11. public struct SerializableGuid : ISerializationCallbackReceiver {
    12.     private Guid guid;
    13.     [SerializeField] private string serializedGuid;
    14.  
    15.     public SerializableGuid(Guid guid) {
    16.         this.guid = guid;
    17.         serializedGuid = null;
    18.     }
    19.  
    20.     public override bool Equals(object obj) {
    21.         return obj is SerializableGuid guid &&
    22.                 this.guid.Equals(guid.guid);
    23.     }
    24.  
    25.     public override int GetHashCode() {
    26.         return -1324198676 + guid.GetHashCode();
    27.     }
    28.  
    29.     public void OnAfterDeserialize() {
    30.         try {
    31.             guid = Guid.Parse(serializedGuid);
    32.         } catch {
    33.             guid = Guid.Empty;
    34.             Debug.LogWarning($"Attempted to parse invalid GUID string '{serializedGuid}'. GUID will set to System.Guid.Empty");
    35.         }
    36.     }
    37.  
    38.     public void OnBeforeSerialize() {
    39.         serializedGuid = guid.ToString();
    40.     }
    41.  
    42.     public override string ToString() => guid.ToString();
    43.  
    44.     public static bool operator ==(SerializableGuid a, SerializableGuid b) => a.guid == b.guid;
    45.     public static bool operator !=(SerializableGuid a, SerializableGuid b) => a.guid != b.guid;
    46.     public static implicit operator SerializableGuid(Guid guid) => new SerializableGuid(guid);
    47.     public static implicit operator Guid(SerializableGuid serializable) => serializable.guid;
    48.     public static implicit operator SerializableGuid(string serializedGuid) => new SerializableGuid(Guid.Parse(serializedGuid));
    49.     public static implicit operator string(SerializableGuid serializedGuid) => serializedGuid.ToString();
    50. }
    51.  
    Property Drawer class:
    Code (CSharp):
    1. using BadCupcake.Cyclic.Item;
    2. using BadCupcake.Cyclic.Utility;
    3. using System;
    4. using UnityEditor;
    5. using UnityEngine;
    6.  
    7. /// <summary>
    8. /// Property drawer for SerializableGuid
    9. ///
    10. /// Author: Searous
    11. /// </summary>
    12. [CustomPropertyDrawer(typeof(SerializableGuid))]
    13. public class SerializableGuidPropertyDrawer : PropertyDrawer {
    14.  
    15.     private float ySep = 20;
    16.     private float buttonSize;
    17.  
    18.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    19.         // Start property draw
    20.         EditorGUI.BeginProperty(position, label, property);
    21.  
    22.         // Get property
    23.         SerializedProperty serializedGuid = property.FindPropertyRelative("serializedGuid");
    24.  
    25.         // Draw label
    26.         position = EditorGUI.PrefixLabel(new Rect(position.x, position.y + ySep / 2, position.width, position.height), GUIUtility.GetControlID(FocusType.Passive), label);
    27.         position.y -= ySep / 2; // Offsets position so we can draw the label for the field centered
    28.  
    29.         buttonSize = position.width / 3; // Update size of buttons to always fit perfeftly above the string representation field
    30.  
    31.         // Buttons
    32.         if(GUI.Button(new Rect(position.xMin, position.yMin, buttonSize, ySep - 2), "New")) {
    33.             serializedGuid.stringValue = Guid.NewGuid().ToString();
    34.         }
    35.         if(GUI.Button(new Rect(position.xMin + buttonSize, position.yMin, buttonSize, ySep - 2), "Copy")) {
    36.             EditorGUIUtility.systemCopyBuffer = serializedGuid.stringValue;
    37.         }
    38.         if(GUI.Button(new Rect(position.xMin + buttonSize * 2, position.yMin, buttonSize, ySep - 2), "Empty")) {
    39.             serializedGuid.stringValue = ItemStack.emptyItemId.ToString();
    40.         }
    41.  
    42.         // Draw fields - passs GUIContent.none to each so they are drawn without labels
    43.         Rect pos = new Rect(position.xMin, position.yMin + ySep, position.width, ySep - 2);
    44.         EditorGUI.PropertyField(pos, serializedGuid, GUIContent.none);
    45.  
    46.         // End property
    47.         EditorGUI.EndProperty();
    48.     }
    49.  
    50.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    51.         // Field height never changes, so ySep * 2 will always return the proper hight of the field
    52.         return ySep * 2;
    53.     }
    54. }
    55.  
    Hope this helps someone :) If you find issues with it please tell me!

    EDIT: Added a Debug.LogWarning() call when a GUID string fails to parse.
    EDIT2: Here's an example of what it looks like:

     
    Last edited: Apr 1, 2021
  9. Roshinator

    Roshinator

    Joined:
    Feb 2, 2019
    Posts:
    13
    Thank you this is perfect!
     
  10. LT_Schmiddy

    LT_Schmiddy

    Joined:
    Feb 9, 2018
    Posts:
    1
    Holy Crow, this looks like EXACTLY what I needed. Thanks.

    You should absolutely make this a package on UPM/OpenUPM, or at least via Git.
     
    Roshinator likes this.
  11. SearousK

    SearousK

    Joined:
    Mar 21, 2020
    Posts:
    6
    I made my SerializableGuid thingy a package as suggested. You can find it here: https://github.com/Searous/Unity.SerializableGuid

    It works locally, but I was having trouble with my git stuff so I couldn't test adding it from the repo. If you have trouble with it, please tell me.
     
  12. Drum_stick

    Drum_stick

    Joined:
    Mar 17, 2020
    Posts:
    1
    thank you for your help.In my project , I used GUids a lot, and I found that the performance of serialization and deserialization with String was very poor.Changing string to byte[] improves performance considerably
    upload_2021-9-9_16-54-31.png
     
    kloogens, oAzuehT, samanabo and 2 others like this.
  13. bunp

    bunp

    Joined:
    Feb 21, 2019
    Posts:
    66
    Sorry for the necro but I am trying to use this serialized Guid and I can't figure out how to assign to it in code.

    upload_2022-1-21_16-1-56.png

    How would I assign to the serialied guid in code?
     
  14. kloogens

    kloogens

    Joined:
    Jul 1, 2015
    Posts:
    107

    Awesome can you share the code for that?
    I don't have a lot of experience dealing with the editor properties.
     
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,379
    While not the same person.

    I personally don't use the array and instead just mirrored the struct layout of the System.Guid, marked it serializable, and then had conversion methods to easily convert between the 2 types:

    https://github.com/lordofduct/space...acepuppy.core/Runtime/src/SerializableGuid.cs

    You should note at this line specifically:
    https://github.com/lordofduct/space...ppy.core/Runtime/src/SerializableGuid.cs#L183
    Code (csharp):
    1.         public unsafe static implicit operator SerializableGuid(System.Guid guid)
    2.         {
    3.             return *(SerializableGuid*)&guid;
    4.         }
    This unsafe coercion relies on the fact that System.Guid and SerializableGuid have the same struct layout. This would fail otherwise. The rest of the struct doesn't particularly care about layout. But by doing this we avoid creating the byte array all together.

    And here is the PropertyDrawer I use, though mind you, there is some minor reliance on code that isn't unity specific. So this property drawer will need massaging to get working in your own project.

    https://github.com/lordofduct/space...or/src/Core/SerializableGuidPropertyDrawer.cs

    And here is an example usage:
    Code (csharp):
    1. public class SomeObject : ScriptableObject
    2. {
    3.     #region Fields
    4.  
    5.     [SerializeField]
    6.     [SerializableGuid.Config(LinkToAsset = true)]
    7.     private SerializableGuid _guid;
    8.  
    9.     #endregion
    10.  
    11.     #region Properties
    12.  
    13.     public System.Guid Guid => _guid; //implicitly is converted to System.Guid
    14.  
    15.     #endregion
    16.  
    17. }
    No need to wrap serialization callbacks or anything.

    Also the 'config' attribute offers the option to tether it to some existing guid. In this case the guid will reflect the asset guid of the ScriptableObject created on disk.
     
    Last edited: Sep 30, 2023
    kloogens and Nad_B like this.
  16. kloogens

    kloogens

    Joined:
    Jul 1, 2015
    Posts:
    107
    Very nice, much appreciated.