applejag/Newtonsoft.Json-for-Unity

Bug: Deserializing Nullable arrays on iOS fails (exception)

Noblauch opened this issue · 2 comments

Expected behavior

Deserializing int?[] should work on all platforms.

Actual behavior

Deserializing int?[] works in the editor and on Android, but not iOS.

Steps to reproduce

  • New project
  • Import jillejr.newtonsoft.json-for-unity via UPM
  • Add following script to scene:
    private string json = "{'NullableArray': [1, null, 2]}".Replace("'", "\"");

    void Start()
    {
        Debug.Log("Deserialize...");
        var dc = JsonConvert.DeserializeObject<SimpleResponse>(json);
        Debug.Log(
            $"NullableArray: {dc.NullableArray.Length}\n" +
            $"[0]:{dc.NullableArray[0].HasValue}\n" +
            $"[1]:{dc.NullableArray[1].HasValue}\n" +
            $"[2]:{dc.NullableArray[2].HasValue}");
    }
    
    public class SimpleResponse
    {
        public int?[] NullableArray { get; set; }
    }
  • Build the game for iOS and run it.
  • Observe the Xcode console output exception:
Xcode Exception output: Deserialize... DeserializeClass:Start() ExecutionEngineException: Attempting to call method 'System.Collections.Generic.List`1[[System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.cctor' for which no ahead of time (AOT) code was generated. at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Utilities.LateBoundReflectionDelegateFactory+<>c__DisplayClass5_0`1[T].b__1 () [0x00000] in <00000000000000000000000000000000>:0 at System.Func`1[TResult].Invoke () [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonArrayContract.CreateTemporaryCollection () [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewList (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, System.Boolean& createdFromNonDefaultCreator) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <00000000000000000000000000000000>:0 at DeserializeClass.Start () [0x00000] in <00000000000000000000000000000000>:0 Rethrow as TypeInitializationException: The type initializer for 'System.Collections.Generic.List>' threw an exception. at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Utilities.LateBoundReflectionDelegateFactory+<>c__DisplayClass5_0`1[T].b__1 () [0x00000] in <00000000000000000000000000000000>:0 at System.Func`1[TResult].Invoke () [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonArrayContract.CreateTemporaryCollection () [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewList (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, System.Boolean& createdFromNonDefaultCreator) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <00000000000000000000000000000000>:0 at DeserializeClass.Start () [0x00000] in <00000000000000000000000000000000>:0 Rethrow as TargetInvocationException: Exception has been thrown by the target of an invocation. at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Utilities.LateBoundReflectionDelegateFactory+<>c__DisplayClass5_0`1[T].b__1 () [0x00000] in <00000000000000000000000000000000>:0 at System.Func`1[TResult].Invoke () [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonArrayContract.CreateTemporaryCollection () [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewList (Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonArrayContract contract, System.Boolean& createdFromNonDefaultCreator) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.Object existingValue, System.String id) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue (Newtonsoft.Json.Serialization.JsonProperty property, Newtonsoft.Json.JsonConverter propertyConverter, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerProperty, Newtonsoft.Json.JsonReader reader, System.Object target) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject (System.Object newObject, Newtonsoft.Json.JsonReader reader, Newtonsoft.Json.Serialization.JsonObjectContract contract, Newtonsoft.Json.Serialization.JsonProperty member, System.String id) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType, Newtonsoft.Json.Serialization.JsonContract contract, Newtonsoft.Json.Serialization.JsonProperty member, Newtonsoft.Json.Serialization.JsonContainerContract containerContract, Newtonsoft.Json.Serialization.JsonProperty containerMember, System.Object existingValue) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize (Newtonsoft.Json.JsonReader reader, System.Type objectType, System.Boolean checkAdditionalContent) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonSerializer.DeserializeInternal (Newtonsoft.Json.JsonReader reader, System.Type objectType) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonConvert.DeserializeObject (System.String value, System.Type type, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.JsonConvert.DeserializeObject[T] (System.String value, Newtonsoft.Json.JsonSerializerSettings settings) [0x00000] in <00000000000000000000000000000000>:0 at DeserializeClass.Start () [0x00000] in <00000000000000000000000000000000>:0

Details

  • Unity build target: iOS
  • Newtonsoft.Json-for-Unity package version: 13.0.102
  • I was using Unity version 2020.3.0f1

Hi, thanks for reporting this @Noblauch! And nice usage of the <details> tag there!

The error might be a bit hard to read, but the important part here is:

Attempting to call method 'System.Collections.Generic.List`1[[System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]::.cctor' for which no ahead of time (AOT) code was generated.

Which could be rewritten as

Attempting to call method List<int?>.cctor() for which no ahead of time (AOT) code was generated.

To solve it

We must ensure that the List<int?>.cctor() method is generated and preserved.

One of the easiest ways of doing this is to use the AotHelper.EnsureList<T> helper method that's included in this fork of Newtonsoft.Json.

using Newtonsoft.Json.Utilities;
using UnityEngine.Scripting;

public class AotTypeEnforcer
{
    [Preserve]
    private void EnsureTypes()
    {
        AotHelper.EnsureList<int?>();
    }
}

The above code just needs to be compiled together with your Unity project. Simplest way to do that is to just add that snippet to a script file inside your Assets directory.

Hope that resolves your issue.


If you want to dig in more of why this code is needed, I've written a bunch of docs on the matter over at https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-using-AotHelper and https://github.com/jilleJr/Newtonsoft.Json-for-Unity/wiki/Fix-AOT-compilation-errors

For reference, cctor stands for the "Class ConsTructOR". Aka static constructor (https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors)

Wow, thanks for the lightning fast response! I tested it out and it worked, cool! A pity that you have to declare it explicitly, but I'm sure this is not possible otherwise..
However I found out, that declaring:
public class SimpleResponse { public int?[] NullableArray { get; set; } }
also "fixes" the issue, and you don't have to use the AotHelper to include the type.

Its unlucky for us, if other developers design code and test in the editor, these issues may not see the daylight for a while.
I call this closed for now, thanks! And keep up the good work! ;)

PS: After fixing this I ran into the next issue, I'll post another ticket soon :-*