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;
public class Demo : MonoBehaviour {
void Start () {
var dummyPlayer = new DummyPlayer ();
dummyPlayer.name = "clayton";
dummyPlayer.isRegistered = true;
dummyPlayer.level = 10;
dummyPlayer.Collectables.Add ("sword");
dummyPlayer.Collectables.Add ("shield");
dummyPlayer.Collectables.Add ("cloak");
var settings = new JsonSerializerSettings ();
settings.TypeNameHandling = TypeNameHandling.Auto;
var json = JsonConvert.SerializeObject (dummyPlayer, settings);
//OUTPUT - {"name":"clayton","level":10,"isRegistered":true,"Collectables":["sword","shield","cloak"]}
Debug.Log ("JSON - " + json);
//ERROR - error while processing request 'variables' (exception: Object reference not set to an instance of an object)
var result = JsonConvert.DeserializeObject<DummyPlayer> (json);
Debug.Log ("RESULT - " + result.ToString ());
}
}
public class DummyPlayer {
public string name;
public int level;
public bool isRegistered;
public HashSet<string> Collectables { get; set; }
public DummyPlayer () {
Collectables = new HashSet<string> ();
}
public override string ToString () {
var sb = new System.Text.StringBuilder ();
sb.AppendLine ("Name : " + name);
sb.AppendLine ("Level : " + level);
sb.AppendLine ("Is registered : " + isRegistered);
sb.AppendLine ("Collectables");
if (Collectables != null) {
foreach (var collectable in Collectables) {
sb.AppendLine ("\t" + collectable);
}
}
return sb.ToString ().TrimEnd ();
}
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?
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?
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.
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.
Yes confirmed that just now. Dictionary<string, int> and List work well. I am migrating another project (hence the demo) and don’t want to change a lot of stuff at this point.
[Serializable]
public class SerializableHashSet<T> : HashSet<T>
{
[JsonConstructor]
public SerializableHashSet(IEnumerable<T> collection) : base(collection) {}
}
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.
What works reliably for me is calling these static methods once ahead of (de-)serializations:
Newtonsoft.Json.Utilities.AotHelper.EnsureList<string>();
Newtonsoft.Json.Utilities.AotHelper.EnsureList<int>();
// ... 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.
Had the same problem , but it can be easily replaced with List , for sure that I do not suggest to move from HashSet to List inside the original structure , but instead make a DTO version for deserializing (also in this way you’re assured for future incompatibilities , see it as a proxy) .
The AOTHelper.Ensure…() functions don’t need to be actually called. They just need to be in your code somewhere and not stripped out. Just put this in an appropriate utility class. Note the [Preserve] to prevent code stripping.