Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more..
    Dismiss Notice
  3. Dismiss Notice

Cannot deserialize dictionaries anymore on Android builds after Unity upgrade

Discussion in 'Android' started by drazuerg, Nov 13, 2021.

  1. drazuerg

    drazuerg

    Joined:
    Jan 31, 2014
    Posts:
    46
    Hello all,

    So I've upgrade my project from Unity 2020.1.2f1 to 2021.2.2f1.
    Performance is smoother so I would like to keep this version.
    BUT, my save/load system seams not to work anymore. It works when I run my game in Editor, and in standalone Android build when made with MONO, but when I run a standalone build with il2cpp, it doesn't work anymore.
    I have some classes that I get serialized well, but the ones that have dictionaries in them cause issues.

    Here is an example of class that doesn't work :
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [System.Serializable]
    6. public class ShapesData
    7. {
    8.  
    9.     //Levels et temps d'upgrades
    10.     public Dictionary<string, int> shapesLevel;
    11.  
    12.     public Dictionary<string, bool> shapesIsNew;
    13.     public Dictionary<string, System.DateTime> shapesStartUpgradeDateTimes;
    14.  
    15.     //Combos
    16.     public Dictionary<string, bool> isComboLinesNewDictionary;
    17.     public Dictionary<string, bool> hasComboLinesNewBeenSeenDictionary;
    18.  
    19.     //Stats
    20.     public Dictionary<string, float> shapesCastCount; //le nb de fois qu'on a dessiné telle ou telle shape
    21.     public Dictionary<string, float> shapesCastCumulativePrecision; //la précision, en cumul (il faudra diviser par count ci dessus)
    22.     public Dictionary<string, float> shapesCastCumulativeDrawTime;
    23.  
    24.  
    25.     public ShapesData(TouchManager touchManager)
    26.     {
    27.         shapesLevel = touchManager.templatesLevels;
    28.         shapesIsNew = touchManager.templatesIsNew;
    29.         shapesStartUpgradeDateTimes = touchManager.gameManager.timeManager.shapesUpgradeOverDateTimes;
    30.  
    31.         isComboLinesNewDictionary = touchManager.isComboLinesNewDictionary;
    32.         hasComboLinesNewBeenSeenDictionary = touchManager.hasComboLinesNewBeenSeenDictionary;
    33.  
    34.         shapesCastCount = touchManager.shapesCastCount;
    35.         shapesCastCumulativePrecision = touchManager.shapesCastCumulativePrecision;
    36.         shapesCastCumulativeDrawTime = touchManager.shapesCastCumulativeDrawTime;
    37.     }
    38.  
    39.     public ShapesData(TouchManager touchManager, bool reset) //si reset = true, on reset toutes les shapes : ATTENTION !!!!
    40.     {
    41.         if(reset == true)
    42.         {
    43.             shapesLevel = touchManager.templatesLevelsBlank;
    44.             shapesIsNew = touchManager.templatesIsNewBlank;
    45.  
    46.             shapesStartUpgradeDateTimes = touchManager.gameManager.timeManager.shapesUpgradeOverDateTimesBlank;
    47.  
    48.             isComboLinesNewDictionary = touchManager.isComboLinesNewBlankDictionary;
    49.             hasComboLinesNewBeenSeenDictionary = touchManager.hasComboLinesNewBeenSeenBlankDictionary;
    50.  
    51.             shapesCastCount = touchManager.shapesCastCountBlank;
    52.             shapesCastCumulativePrecision = touchManager.shapesCastCumulativePrecisionBlank;
    53.             shapesCastCumulativeDrawTime = touchManager.shapesCastCumulativeDrawTimeBlank;
    54.         }
    55.     }
    56.  
    57.  
    58. }

    And this is my SAVE code :
    Code (CSharp):
    1.    public static void SaveShapes(TouchManager touchManager)
    2.     {
    3.         BinaryFormatter formatter = new BinaryFormatter();
    4.         string path = Application.persistentDataPath + "/shapes.save";
    5.         FileStream stream = new FileStream(path, FileMode.Create);
    6.         ShapesData data = new ShapesData(touchManager);
    7.         formatter.Serialize(stream, data);
    8.  
    9.  
    10.         stream.Close();
    11.     }
    12.  
    and my LOAD code :

    Code (CSharp):
    1.   public static ShapesData LoadShapes(TouchManager touchManager)
    2.     {
    3.         string path = Application.persistentDataPath + "/shapes.save";
    4.         if (File.Exists(path))
    5.         {
    6.             BinaryFormatter formatter = new BinaryFormatter();
    7.             FileStream stream = new FileStream(path, FileMode.Open);
    8.             ShapesData data = formatter.Deserialize(stream) as ShapesData;
    9.             stream.Close();
    10.             return data;
    11.         }
    12.         else
    13.         {
    14.             Debug.LogError("save file not found in " + path);
    15.             return LoadBlankShapesData(touchManager);
    16.         }
    17.     }

    It worked well with the previous unity version.
    I even tried to reset my phone in case some save file was hiding somewhere and causing incompatibility but no success either.
    Phone is Mi9T, running MiUI12.
    When attaching it to Visual Studio, I get this exception : System.InvalidCastException: Object must implement IConvertible.


    My New / Old player settings : https://imgur.com/a/PJynbFv


    Any help would be much appreciated, this has been driving me crazy for the last few days !

    Thanks !
     
  2. drazuerg

    drazuerg

    Joined:
    Jan 31, 2014
    Posts:
    46
    After further investigation on a small scale test project, it seems like using IL2CPP, I can declare several dictionaries with different types in the class I want to serialize, but as soon as the constructors use dictionaries of more than one type (example <string, int> and <string, bool>) then deserialization won't work.

    This work in MONO and IL2CPP, constructors only use <string, int> dictionaries.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. [System.Serializable]
    7. public class TestData
    8. {
    9.     public Dictionary<string, int> testDico;
    10.     public Dictionary<string, int> testDico2;
    11.     public Dictionary<string, bool> testDicoBool;
    12.  
    13.     public TestData(TestManager testManager, bool blank)
    14.     {
    15.         if(blank == true)
    16.         {
    17.             testDico = testManager.testDicoBlank;
    18.             testDico2 = testManager.testDicoInt2;
    19.             //testDicoBool = testManager.testDicoBoolBlank;
    20.         }
    21.     }
    22.     public TestData(TestManager testManager)
    23.     {
    24.         testDico = testManager.testDico;
    25.         testDico2 = testManager.testDicoInt2;
    26.         //testDicoBool = testManager.testDicoBoolBlank;
    27.     }
    28.  
    29. }
    This works in MONO but NOT in IL2CPP, constructors use both <string, int> and <string, bool> dictionaries.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. [System.Serializable]
    7. public class TestData
    8. {
    9.     public Dictionary<string, int> testDico;
    10.     public Dictionary<string, int> testDico2;
    11.     public Dictionary<string, bool> testDicoBool;
    12.  
    13.     public TestData(TestManager testManager, bool blank)
    14.     {
    15.         if(blank == true)
    16.         {
    17.             testDico = testManager.testDicoBlank;
    18.             testDico2 = testManager.testDicoInt2;
    19.             testDicoBool = testManager.testDicoBoolBlank;
    20.         }
    21.     }
    22.     public TestData(TestManager testManager)
    23.     {
    24.         testDico = testManager.testDico;
    25.         testDico2 = testManager.testDicoInt2;
    26.         testDicoBool = testManager.testDicoBoolBlank;
    27.     }
    28.  
    29. }
    30.  

    Weird, no ?
    And if the only workaround is to use only one type of variables inside the class I need to serialize, any hint on how to do it ?

    Also as a side note, issue seems to apply only to dictionaries.
    I've tried below adding other random variables to the class (a hint and a string), and as long as dictionaries are the same types, this works fine in IL2CPP :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5.  
    6. [System.Serializable]
    7. public class TestData
    8. {
    9.     public Dictionary<string, int> testDico;
    10.     public Dictionary<string, int> testDico2;
    11.     public Dictionary<string, bool> testDicoBool;
    12.     public int toto;
    13.     public string tata;
    14.  
    15.     public TestData(TestManager testManager, bool blank)
    16.     {
    17.         if(blank == true)
    18.         {
    19.             testDico = testManager.testDicoBlank;
    20.             testDico2 = testManager.testDicoInt2;
    21.             //testDicoBool = testManager.testDicoBoolBlank;
    22.  
    23.             toto = 1;
    24.             tata = "tata";
    25.         }
    26.     }
    27.     public TestData(TestManager testManager)
    28.     {
    29.         testDico = testManager.testDico;
    30.         testDico2 = testManager.testDicoInt2;
    31.         //testDicoBool = testManager.testDicoBoolBlank;
    32.  
    33.         toto = 1;
    34.         tata = "tata";
    35.     }
    36.  
    37. }
    38.  
     
    Last edited: Nov 13, 2021
  3. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,773
    Can you provide the full error message you see, including a stack trace for the exception, if one exists?
     
  4. King-Krool

    King-Krool

    Joined:
    Nov 30, 2017
    Posts:
    2
    I am also having a very similar issue.

    Code (CSharp):
    1.  
    2. Error
    3. Nov 16, 2021, 09:38:50.454
    4. 298
    5. System.InvalidCastException: Object must implement IConvertible. at System.Convert.ChangeType (System.Object value, System.Type conversionType, System.IFormatProvider provider) [0x00000] in <00000000000000000000000000000000>:0 at System.Runtime.Serialization.SerializationInfo.GetValue (System.String name, System.Type type) [0x00000] in <00000000000000000000000000000000>:0 at System.Collections.Generic.Dictionary`2[TKey,TValue].OnDeserialization (System.Object sender) [0x00000] in <00000000000000000000000000000000>:0 at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize (System.Runtime.Remoting.Messaging.HeaderHandler handler, System.Runtime.Serialization.Formatters.Binary.__BinaryParser serParser, System.Boolean fCheck) [0x00000] in <00000000000000000000000000000000>:0 at CharacterManager.LoadPlayerData (System.Int32 id, System.Boolean verbose) [0x00000] in <00000000000000000000000000000000>:0 at GuildList.CreateCard (Player+PlayerData newPlayerData) [0x00000] in <0000000
    6.  
    This is the object type that im deserializing:
    Code (CSharp):
    1.  
    2. [System.Serializable]
    3.     public class PlayerData
    4.     {
    5.         public int ID;
    6.         public string currentVersion;
    7.  
    8.         public bool isYou = false;
    9.  
    10.         public Gear.GearData helmet;
    11.         public Gear.GearData neck;
    12.         public Gear.GearData shoulder;
    13.         public Gear.GearData back;
    14.         public Gear.GearData chest;
    15.         public Gear.GearData wrist;
    16.         public Gear.GearData hands;
    17.         public Gear.GearData waist;
    18.         public Gear.GearData legs;
    19.         public Gear.GearData feet;
    20.         public Gear.GearData ring1;
    21.         public Gear.GearData ring2;
    22.         public Gear.GearData trinket1;
    23.         public Gear.GearData trinket2;
    24.         public Gear.GearData mainhand;
    25.         public Gear.GearData offhand;
    26.         public Gear.GearData ranged;
    27.  
    28.         public Gear.GearData[] inventory;
    29.         public int currentInventoryPointer;
    30.  
    31.         public SkillTree.TreeData tree1;
    32.         public SkillTree.TreeData tree2;
    33.         public SkillTree.TreeData tree3;
    34.         public string primaryAITree;
    35.         public string secondaryAITree;
    36.         public int primaryAITreeNumber;
    37.         public int secondaryAITreeNumber;
    38.         public bool autoAssignSkills = true;
    39.      
    40.         public Dictionary<CharacterManager.QuirkType, CharacterManager.Quirk> quirks = new Dictionary<CharacterManager.QuirkType, CharacterManager.Quirk>();
    41.  
    42.         public Dictionary<BenefitManager.BenefitType, float> benefits = new Dictionary<BenefitManager.BenefitType, float>();
    43.  
    44.         public Dictionary<string, float> friendXPTable = new Dictionary<string, float>();
    45.  
    46.         public string headSpriteName;
    47.         public string earsSpriteName;
    48.         public string hairSpriteName;
    49.         public string eyebrowsSpriteName;
    50.         public string eyesSpriteName;
    51.         public string mouthSpriteName;
    52.         public string beardSpriteName;
    53.         public string skinColorName;
    54.         public string eyeColorName;
    55.         public string hairColorName;
    56.  
    57.         public string guildName;
    58.         public string playerName;
    59.         public string description;
    60.         public float heightScale;
    61.         public float widthScale;
    62.  
    63.         public int level;
    64.         public int skillPoints;
    65.         public float expToNextLevel;
    66.         public float expIntoLevel;
    67.  
    68.         public CharClass myCharClass;
    69.         public CharRace myCharRace;
    70.  
    71.         public int happiness;
    72.  
    73.         //public float speed;
    74.         //public float range;
    75.         //public float attackSpeed;
    76.         //public float damage;
    77.         public float currentHealth;
    78.         public float currentMana;
    79.         //public float healthMax;
    80.         public float baseRegenSpeed = 5;
    81.  
    82.         public float baseDodgeChance = 0.05f;
    83.         public float baseMissChance = 0.05f;
    84.         public float baseParryChance = 0.05f;
    85.  
    86.         //public float critChance;
    87.         //public float critMultiplier;
    88.  
    89.         //public int endurance;
    90.         //public int regeneration;
    91.         //public int intellect;
    92.         //public int strength;
    93.         //public int agility;
    94.  
    95.         //public int permEnduranceAdd;
    96.         //public int permRegenerationAdd;
    97.         //public int permIntellectAdd;
    98.         //public int permStrengthAdd;
    99.         //public int permAgilityAdd;
    100.         //public int permEnduranceMult;
    101.         //public int permRegenerationMult;
    102.         //public int permIntellectMult;
    103.         //public int permStrengthMult;
    104.         //public int permAgilityMult;
    105.  
    106.         //public int armor;
    107.         //public int block;
    108.  
    109.         public float[] hairColor;
    110.         public float[] skinColor;
    111.  
    112.         public float[] eyeColor;
    113.  
    114.         public string[] beatenLevels;
    115.  
    116.         //public Ability.AbilityData[] abilities;
    117.         public Dictionary<string, Ability.AbilityData> abilities = new Dictionary<string, Ability.AbilityData>();
    118.         public bool autoPlay = true;
    119.         public Ability.AbilityData abilityToUse;
    120.  
    121.         public Ability.AbilityData activeAbility1;
    122.         public Ability.AbilityData activeAbility2;
    123.         public Ability.AbilityData activeAbility3;
    124.         public Ability.AbilityData activeAbility4;
    125.  
    126.         public float attackTimer;
    127.         public float castTimer;
    128.         public float originalCastTime;
    129.  
    130.         public int gold;
    131.  
    132.         public bool quit;
    133. }
    134.  
    Notes:
    1. This is only experience in HTML or iOS (haven't tested on android). I cannot replicate in editor.
    2. This happened after updating unity in a similar fashion to OP
    3. The error code is from the devops cloud diagnostics error reporting.
    4. I am actively deleting all old data on launch for debugging so its not trying to deserialize an old data object but instead one that was serialized moments before.
     
    Last edited: Nov 16, 2021
  5. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,773
    Do you know if the saved data was serialized with the old version of Unity? Between the two versions the OP mentioned, we did update the version of Mono, which means that the data layout of some types has changed. Binary Formatter depends on the data layout, so that might be the issue.
     
  6. drazuerg

    drazuerg

    Joined:
    Jan 31, 2014
    Posts:
    46
    Hello @JoshPeterson

    Same as @King-Krool, I erased data so it was both serialized and deserialized with the updated version of Unity (what is OP by the way ?).

    I plugged my phone to Unity to read logs from the editor, here is what I got, I hope it helps :



    AndroidPlayer "Xiaomi_Mi 9T@ADB:a9786a4d:0" InvalidCastException: Object must implement IConvertible.
    at System.Convert.ChangeType (System.Object value, System.Type conversionType, System.IFormatProvider provider) [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.FormatterConverter.Convert (System.Object value, System.Type type) [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.SerializationInfo.GetValue (System.String name, System.Type type) [0x00000] in <00000000000000000000000000000000>:0
    at System.Collections.Generic.Dictionary`2[TKey,TValue].OnDeserialization (System.Object sender) [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.ObjectManager.RaiseDeserializationEvent () [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize (System.Runtime.Remoting.Messaging.HeaderHandler handler, System.Runtime.Serialization.Formatters.Binary.__BinaryParser serParser, System.Boolean fCheck) [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler, System.Boolean fCheck) [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) [0x00000] in <00000000000000000000000000000000>:0
    at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) [0x00000] in <00000000000000000000000000000000>:0
    at SaveSystem.LoadShapes (TouchManager touchManager) [0x0007b] in C:\Unity\Spelltouch_2021.1.2\Assets\Scripts\SaveSystem.cs:67
    at GameManager.LoadShapes () [0x00007] in C:\Unity\Spelltouch_2021.1.2\Assets\Scripts\GameManager.cs:682
    at GameManager.Load () [0x0002a] in C:\Unity\Spelltouch_2021.1.2\Assets\Scripts\GameManager.cs:527
    at GameManager.Start () [0x0019b] in C:\Unity\Spelltouch_2021.1.2\Assets\Scripts\GameManager.cs:393
     
  7. JoshPeterson

    JoshPeterson

    Unity Technologies

    Joined:
    Jul 21, 2014
    Posts:
    6,773
    Thanks for the details. Can you submit a bug report for this issue?

    https://unity3d.com/unity/qa/bug-reporting
     
  8. drazuerg

    drazuerg

    Joined:
    Jan 31, 2014
    Posts:
    46
    Just did, Case 1384027 ; I used the link to this thread as the description, I hope it's OK.
    Thanks as always for the thorough support !
     
    JoshPeterson likes this.
  9. LorisToia

    LorisToia

    Joined:
    Jan 21, 2021
    Posts:
    14
    I'm having the same issue.
    dictionary saved on ScriptableObject works fine on Unity 2021.2 but doesn't works on Android Build for oculus (didn't tested other build, but i need this)
     
  10. FlyVC

    FlyVC

    Joined:
    Jan 5, 2020
    Posts:
    24
    I ran into the same issue

    @drazuerg did you find a resolution for this?
     
  11. drazuerg

    drazuerg

    Joined:
    Jan 31, 2014
    Posts:
    46
    Nope :(
    I kinda abandonned this project actually ... Still interested in a solution though!