Search Unity

Resolved JSON deserialize nullable on iOS

Discussion in 'Scripting' started by Noblauch, Apr 15, 2021.

  1. Noblauch

    Noblauch

    Joined:
    May 23, 2017
    Posts:
    275
    Hi, we (as almost everyone) are using the JSON .NET For Unity package to deserialize our JSON objects that come from the server.

    However, it works in the editor and on Android, but on iOS the deserialization of nullable types crashes. Here is the minimum code to reproduce:

    Code (CSharp):
    1. public class SimpleResponse
    2. {
    3.     public List<int?> NullableList { get; set; }
    4. }
    Code (CSharp):
    1. public class DeserializeClass : MonoBehaviour
    2. {
    3.     private string json = "{'NullableList': [1, null, 2]}".Replace("'", "\"");
    4.  
    5.     void Start()
    6.     {
    7.         Debug.Log("Deserialize...");
    8.         var deserializedClass = JsonConvert.DeserializeObject<SimpleResponse>(json);
    9.         Debug.Log($"NullableList: {deserializedClass.NullableList.Count}\n" +
    10.                   $"[0]:{deserializedClass.NullableList[0].HasValue}\n" +
    11.                   $"[1]:{deserializedClass.NullableList[1].HasValue}\n" +
    12.                   $"[2]:{deserializedClass.NullableList[2].HasValue}");
    13.     }
    14. }
    As you can see, its pretty straight forward, throw a nullable type into the json and the whole thing explodes. (Remember, works in the Editor)

    When running on iOS, the deserialisation crashes and the following error is thrown:
    ArgumentNullException: Value cannot be null. Parameter name: method at Newtonsoft.Json.Utilities.ValidationUtils.ArgumentNotNull (System.Object value, System.String parameterName) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Utilities.LateBoundReflectionDelegateFactory.CreateParameterizedConstructor (System.Reflection.MethodBase method) [0x00000] in <00000000000000000000000000000000>:0 at Newtonsoft.Json.Serialization.JsonArrayContract.CreateWrapper (System.Object list) [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.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.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

    I wasted my whole day on this, but whatever I tried, it won't run on iOS.
    This is a very common issue that probably every iOS app developer must have run into. So I hope someone in the department of the more advanced users can give me some hints on that?
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    From someone who has Json in several projects at work and personal, I haven't run into odd behavior from serializing or deserializing between Android/iOS if I'm using the same correctly formatted data. So, the next possible pain point is in the data itself. Have you checked if the json is getting retrieved properly on iOS?

    https://assetstore.unity.com/packages/tools/integration/log-viewer-12047 This can sometimes help by showing you your console at runtime. Just add a bunch of Debug statements so you can view them at runtiime and see what you're getting. The error seems to suggest either your json is incorrect or it's an empty string maybe. But I would start there.
     
  3. Noblauch

    Noblauch

    Joined:
    May 23, 2017
    Posts:
    275
    I finally got it working!
    As mentioned, we were using JSON .NET For Unity from the asset store (which wasn't touched since 2017 at this point). All I tried failed, so in the last hopes I installed another JSON framework: Newtonsoft.Json for Unity by jilleJr and contributors.
    And: It worked! I was able to deserialize List<int?> even on iOS, I didn't find any side effects from swapping yet.
    !! However Newtonsoft.Json for Unity also wasn't able to deserialize int?[] on iOS by default !!
    To get it to work you need to explicitly include the type using their AOT Helper class like this:
    Code (CSharp):
    1. [Preserve]  private void EnsureTypes() => AotHelper.EnsureList<int?>();
    This snippet must be included anywhere in the project, where you can make sure, that the script is included.

    Thanks for your reply, but the JSON was not the problem. As you can see in my original post, I even stripped the problem to a blank project and deserialized the JSON that was hardcoded as a string. The problem definitely lies inside the way iOS or the conversion works. (AOT issues)
     
    Last edited: Apr 16, 2021
    SamuCsernak and austinborden like this.
  4. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    Interesting. Yeah, I haven't used the asset store JSON.NET in a long time either, but I have yet to run into issues with either. Glad you got it figured out.
     
  5. Zarkend

    Zarkend

    Joined:
    May 11, 2016
    Posts:
    28
  6. austinborden

    austinborden

    Joined:
    Aug 5, 2016
    Posts:
    24
    I recently updated Unity to a version that includes the JSON.net framework as a package. This caused conflicts with the Asset Store version I was using so had to switch over. That started producing the same errors mentioned above. Adding the AotHelper calls appears to have fixed things!