Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

Unable to deserialize hashsets

Discussion in 'Scripting' started by claytoncurmi, Sep 2, 2020.

  1. claytoncurmi

    claytoncurmi

    Joined:
    Jul 6, 2010
    Posts:
    168
    Hi all,

    I created a demo project to test a serialization issue I am noticing in a project that was recently migrated to Unity 2020.1.3f1. The problem I am facing is related to the deserialization of a HashSet variable as shown in the code below;

    Code (CSharp):
    1. public class Demo : MonoBehaviour {
    2.  
    3.     void Start () {
    4.         var dummyPlayer = new DummyPlayer ();
    5.         dummyPlayer.name = "clayton";
    6.         dummyPlayer.isRegistered = true;
    7.         dummyPlayer.level = 10;
    8.  
    9.         dummyPlayer.Collectables.Add ("sword");
    10.         dummyPlayer.Collectables.Add ("shield");
    11.         dummyPlayer.Collectables.Add ("cloak");
    12.  
    13.         var settings = new JsonSerializerSettings ();
    14.         settings.TypeNameHandling = TypeNameHandling.Auto;
    15.  
    16.         var json = JsonConvert.SerializeObject (dummyPlayer, settings);
    17.         //OUTPUT - {"name":"clayton","level":10,"isRegistered":true,"Collectables":["sword","shield","cloak"]}
    18.         Debug.Log ("JSON - " + json);
    19.  
    20.         //ERROR - error while processing request 'variables' (exception: Object reference not set to an instance of an object)
    21.         var result = JsonConvert.DeserializeObject<DummyPlayer> (json);
    22.         Debug.Log ("RESULT - " + result.ToString ());
    23.     }
    24. }
    25.  
    26.  
    27. public class DummyPlayer {
    28.         public string name;
    29.         public int level;
    30.         public bool isRegistered;
    31.         public HashSet<string> Collectables { get; set; }
    32.  
    33.         public DummyPlayer () {
    34.                 Collectables = new HashSet<string> ();
    35.         }
    36.  
    37.         public override string ToString () {
    38.                 var sb = new System.Text.StringBuilder ();
    39.                 sb.AppendLine ("Name : " + name);
    40.                 sb.AppendLine ("Level : " + level);
    41.                 sb.AppendLine ("Is registered : " + isRegistered);
    42.                 sb.AppendLine ("Collectables");
    43.  
    44.                 if (Collectables != null) {
    45.                         foreach (var collectable in Collectables) {
    46.                                 sb.AppendLine ("\t" + collectable);
    47.                 }
    48.         }
    49.  
    50.         return sb.ToString ().TrimEnd ();
    51. }
    This works in Editor but not in standalone builds. Is somehow the HashSet class stripped from the project? I included a link.xml to preserve the System.Collections assembly however I am still getting the same error. The project settings have an option to set code stripping with the minimum value being Low. In previous versions there was a possibility to disable it. Can this still be done?

    Thanks
     
    Last edited: Sep 2, 2020
    _TheFuture_ likes this.
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,923
    Unless code stripping is severely broken in that version of Unity, I doubt that's the problem. What's the error you're actually getting? If HashSet wasn't there wouldn't you get an error in the DummyPlayer constructor when an instance of the HashSet was created?
     
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,047
    Well, it would help to know:

    • How exactly does your DummyPlayer class look like so we know how "Collectables" is defined. I would guess it's a
      HashSet<string>
      ?
    • Since you use JsonConvert we would assume that you use the Newtonsoft.Json library but this is not necessarily the case. So please tell us exactly which Json library you use and which version.
    • How does the generated "json" look like. Does the json actually contain those values?
    Feel free to edit your original post and add this information.

    A quick google search revealed this:
    https://github.com/bridgedotnet/Bridge.Newtonsoft.Json/issues/152
     
    PraetorBlue likes this.
  4. claytoncurmi

    claytoncurmi

    Joined:
    Jul 6, 2010
    Posts:
    168
    The error happens on line 21 (error is in the comment in line 20). Regarding second part, yes that is true.
     
  5. claytoncurmi

    claytoncurmi

    Joined:
    Jul 6, 2010
    Posts:
    168
    Yes, I am using the latest Newtonsoft.JSON library using the package manager as defined below;

    Code (CSharp):
    1.     "com.unity.nuget.newtonsoft-json": {
    2.       "version": "2.0.0",
    3.       "depth": 0,
    4.       "source": "registry",
    5.       "dependencies": {},
    6.       "url": "https://packages.unity.com"
    7.     }
    Updated the original post to show the generated JSON on line 17.

    Thanks
     
    _TheFuture_ likes this.
  6. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,923
    See if a different collection type serializes properly. Maybe json.net has a specific issue with HasSet as mentioned in the above link. If that's the case a workaround may be to save it as a list and load the list back into a hashset after you deserialize.
     
  7. claytoncurmi

    claytoncurmi

    Joined:
    Jul 6, 2010
    Posts:
    168
    Yes confirmed that just now. Dictionary<string, int> and List<string> work well. I am migrating another project (hence the demo) and don't want to change a lot of stuff at this point.
     
    _TheFuture_ likes this.
  8. FrostGateDeveloper

    FrostGateDeveloper

    Joined:
    Nov 10, 2015
    Posts:
    4
    @claytoncurmi face up with the same error with HashSet deserialization, have you resolved it?
     
    _TheFuture_ likes this.
  9. FrostGateDeveloper

    FrostGateDeveloper

    Joined:
    Nov 10, 2015
    Posts:
    4
    OK, i found the solution:
    Code (CSharp):
    1.  
    2.     [Serializable]
    3.     public class SerializableHashSet<T> : HashSet<T>
    4.     {
    5.         [JsonConstructor]
    6.         public SerializableHashSet(IEnumerable<T> collection) : base(collection) {}
    7.     }
    8.  
     
    _TheFuture_ and albertdzh like this.
  10. Wattosan

    Wattosan

    Joined:
    Mar 22, 2013
    Posts:
    460
    Is there still no fix in the Newtonsoft package?
     
    _TheFuture_ likes this.
  11. _TheFuture_

    _TheFuture_

    Joined:
    Mar 11, 2016
    Posts:
    4
    This is still a thing. It's throwing an impossible to reason exception deep down the (de-)serialization chain which ends in a "ArgumentNotNull" exception with property name "method" (doesn't exist) for me... It does not happen on Windows Desktop builds or in the editor, I've only had it when exporting WebGL and I'm reading others have it with IOS exports.

    According to Kalle Jillheden (jilleJr), the developer of the previous Json.net for Unity project, this is due to bytecode stripping, as they elaborate here: https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-using-link.xml

    What works reliably for me is calling these static methods once ahead of (de-)serializations:
    Code (CSharp):
    1. Newtonsoft.Json.Utilities.AotHelper.EnsureList<string>();
    2. Newtonsoft.Json.Utilities.AotHelper.EnsureList<int>();
    3. // ... more collection element types here
    where the generic parameter is the element type in the HashSet<>

    @FrostGateDeveloper's solution has only been working for some (not all) HashSets for me.
     
    Last edited: May 27, 2022
    Declan_McG, ePhaedrus and Bunny83 like this.
  12. hugeandy

    hugeandy

    Joined:
    Nov 2, 2016
    Posts:
    135
    Yep we were experiencing this and calling
    Newtonsoft.Json.Utilities.AotHelper.EnsureList<T>()
    worked a treat!
     
    chakaramba likes this.