com.openai.unity: iOS: JsonSerializationException: Cannot deserialize readonly or fixed size dictionary: System.Collections.Generic.IReadOnlyDictionary

ExceptioniOS

Having exception with serialization metadata on iOS, works in Unity Editor and Android.

Unity 2022.3.14f Managed stripping level: Minimal

I believe the issue is with stripping backend which is il2cpp for iOS and Mono for Android. Somehow, on iOS doesn’t support serialization of:

        [Preserve]
        [JsonProperty("metadata")]
        public IReadOnlyDictionary<string, string> Metadata { get; }

Tried to pass null, empty Dictionary, both didn’t work

About this issue

  • Original URL
  • State: closed
  • Created 7 months ago
  • Comments: 22 (13 by maintainers)

Most upvoted comments

That’s working now, great work. thx

This one works on iOS:

Great I’ll push out that release now.

Can we keep the property accessor read only and have the contractor be a dictionary?

This one works on iOS:

class ReadOnlyTest
{
    [Preserve]
    [JsonConstructor]
    public ReadOnlyTest(
    [JsonProperty("metadata")] Dictionary<string, string> metadata)
    {
        Metadata = metadata;
    }

    [Preserve]
    [JsonProperty("metadata")]
    public IReadOnlyDictionary<string, string> Metadata { get; }
}

Don’t know if I can deal with these screenshots 😅 Any chance you can paste the actual text?

I managed to reproduce issue and created test class for it, on iOS JsonConvert.DeserializeObject for IReadOnlyDictionary doesn’t work, probably should be replaced with Dictionary<string, string>.

Here is test class:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Newtonsoft;
using Newtonsoft.Json;
using OpenAI;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Utilities.Rest.Extensions;
using UnityEngine.Scripting;

class ReadOnlyTest
{
    [Preserve]
    [JsonConstructor]
    public ReadOnlyTest(
    [JsonProperty("metadata")] IReadOnlyDictionary<string, string> metadata)
    {
        Metadata = metadata;
    }

    [Preserve]
    [JsonProperty("metadata")]
    public IReadOnlyDictionary<string, string> Metadata { get; }
}

class DictionaryTest
{
    [Preserve]
    [JsonConstructor]
    public DictionaryTest(
    [JsonProperty("metadata")] Dictionary<string, string> metadata)
    {
        Metadata = metadata;
    }

    [Preserve]
    [JsonProperty("metadata")]
    public Dictionary<string, string> Metadata { get; }
}

public class TestSerialization : MonoBehaviour
{
    private Dictionary<string, string> bla = new Dictionary<string, string>()
    {
        ["etst"] = "3232",
        ["lala"] = "lolo"
    };

    public void Test1()
    {
        Debug.Log("Test1");
        var settings = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            DefaultValueHandling = DefaultValueHandling.Ignore,
            Converters = new List<JsonConverter>
            {
                new StringEnumConverter(new SnakeCaseNamingStrategy())
            },
            ContractResolver = new EmptyToNullStringContractResolver()
        };

        

        string value = JsonConvert.SerializeObject(new ReadOnlyTest(bla), settings);
        Debug.Log("SerializeObject : " + value);
        ReadOnlyTest readOnlyTest = JsonConvert.DeserializeObject<ReadOnlyTest>(value);


        Debug.Log("DeserializeObject: ");
        foreach (var pair in readOnlyTest.Metadata)
        {
            Debug.Log(pair.Key + "  " + pair.Value);
        }
    }

    public void Test2()
    {
        Debug.Log("Test2");
        var settings = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            DefaultValueHandling = DefaultValueHandling.Ignore,
            Converters = new List<JsonConverter>
            {
                new StringEnumConverter(new SnakeCaseNamingStrategy())
            },
            ContractResolver = new EmptyToNullStringContractResolver()
        };
        string value = JsonConvert.SerializeObject(new DictionaryTest(bla), settings);
        Debug.Log("SerializeObject : " + value);

        DictionaryTest dictionaryTest = JsonConvert.DeserializeObject<DictionaryTest>(value);
        Debug.Log("DeserializeObject: ");
        foreach (var pair in dictionaryTest.Metadata)
        {
            Debug.Log(pair.Key + "  " + pair.Value);
        }
    }
}

With Test1 click having the same error: TEST1

I’m unfamiliar with iOS but it definitely seems as though a type is getting stripped from the build somehow.

Let me know what you can find and I’ll implement a fix if possible.

Thank you Stephen for fast response. Yes, I tried different types, the whole OpenAI namespace, it didn’t work out. I think the issue is not with stripping but with deserialization IReadOnlyDictionary itself. It can’t deserialise metadata but somehow works fine on Android. I will try to make a build for Android with il2cpp and check if the issue reproduces.