Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Unidentifiable problem in List<> Serialization/Deserialization

Discussion in 'Scripting' started by codevstudios, Jul 12, 2018.

  1. codevstudios

    codevstudios

    Joined:
    Jul 12, 2018
    Posts:
    1
    The error being recieved:
    Code (CSharp):
    1. ArgumentNullException: Argument cannot be null.
    2. Parameter name: elementType
    3. System.Array.CreateInstance (System.Type elementType, System.Int32[] lengths) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System/Array.cs:640)
    4. System.Array.CreateInstance (System.Type elementType, Int32 length) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System/Array.cs:620)
    5. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadListString (System.Xml.Serialization.XmlTypeMapping typeMap, System.String values)
    6. System.Xml.Serialization.XmlSerializationReaderInterpreter.GetValueFromXmlString (System.String value, System.Xml.Serialization.TypeData typeData, System.Xml.Serialization.XmlTypeMapping typeMap)
    7. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadAttributeMembers (System.Xml.Serialization.ClassMap map, System.Object ob, Boolean isValueList)
    8. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadMembers (System.Xml.Serialization.ClassMap map, System.Object ob, Boolean isValueList, Boolean readByOrder)
    9. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstanceMembers (System.Xml.Serialization.XmlTypeMapping typeMap, System.Object ob)
    10. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstance (System.Xml.Serialization.XmlTypeMapping typeMap, Boolean isNullable, Boolean checkType)
    11. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObject (System.Xml.Serialization.XmlTypeMapping typeMap, Boolean isNullable, Boolean checkType)
    12. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObjectElement (System.Xml.Serialization.XmlTypeMapElementInfo elem)
    13. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadMembers (System.Xml.Serialization.ClassMap map, System.Object ob, Boolean isValueList, Boolean readByOrder)
    14. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstanceMembers (System.Xml.Serialization.XmlTypeMapping typeMap, System.Object ob)
    15. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadClassInstance (System.Xml.Serialization.XmlTypeMapping typeMap, Boolean isNullable, Boolean checkType)
    16. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadObject (System.Xml.Serialization.XmlTypeMapping typeMap, Boolean isNullable, Boolean checkType)
    17. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadRoot (System.Xml.Serialization.XmlTypeMapping rootMap)
    18. System.Xml.Serialization.XmlSerializationReaderInterpreter.ReadRoot ()
    19. System.Xml.Serialization.XmlSerializer.Deserialize (System.Xml.Serialization.XmlSerializationReader reader)


    I have used the same code template for 2 different (currently working) data file scripts (one using ints, floats and strings for player data. Another using string arrays and int arrays for inventory/equipment data.). However I have ran into a problem when attempting to serialize a List<>, more specifically when attempting to deserialize and load.

    Here are the pertinent pieces of said script:

    The Save() Function, which to my knowledge is working 100% as intended and is creating the proper file

    Code (CSharp):
    1.     #region Save File
    2.     public void Save()
    3.     {
    4. //Here I am fetching a static object who's children are the focus of our data serialization
    5.         var blockObjects = transform.GetComponentsInChildren<WorldBlockData>();
    6.  
    7. //Run this code for as many children the manager contains
    8.         for ( int i = 0; i < transform.childCount; i++) {
    9.  
    10. //Add the ID of the target object(s) to the data            
    11.  
    12. fileData.b.bdL.Add(EncryptDecrypt(blockObjects[i].blockID.ToString()));
    13.  
    14. //Then do the same for the X and Y positions            
    15.  
    16. fileData.b.bdX.Add(EncryptDecrypt(blockObjects[i].transform.position.x.ToString()));
    17.  
    18.             fileData.b.bdY.Add(EncryptDecrypt(blockObjects[i].transform.position.y.ToString()));
    19.  
    20.         }
    21.  
    22.         // Time to create our XML!
    23.         data = SerializeObject(fileData);
    24.  
    25.         // This is the final resulting XML from the serialization process
    26.         CreateXML();
    27.        
    28.     }
    29.  
    30. #endregion//Save File
    Now to quickly show the "DemoData" class which has been refactored to use Generic Lists

    Code (CSharp):
    1. [XmlRoot("bData")]
    2. public class bData
    3. {
    4.     // We have to define a default instance of the structure
    5.     public LData ld;
    6.     // Default constructor doesn't really do anything at the moment
    7.     public bData() { }
    8.  
    9.     // Anything we want to store in the XML file, we define it here
    10.     public struct LData
    11.     {
    12.  
    13.         public LData(List<string> id, List<string> x, List<string> y)
    14.         {
    15.             bdL = id;
    16.             bdX = x;
    17.             bdY = y;
    18.         }
    19.  
    20.         [XmlAttribute]
    21.         public List<string> bdL;
    22.  
    23.         [XmlAttribute]
    24.         public List<string> bdX;
    25.  
    26.         [XmlAttribute]
    27.         public List<string> bdY;
    28.  
    29.     }
    30.  
    31.     public LData b = new LData(new List<string>(), new List<string>(), new List<string>());
    32.  
    33. }
    And finally, the Load() Function as well as my deserialize method. This is where the problem crops up

    Code (CSharp):
    1. LoadXML();
    2.  
    3.         Debug.LogError("Load(): XML Loaded");
    4.  
    5.         if(data.ToString() != "") {
    6.  
    7.             Debug.LogError("Load(): Found XML non-null string");
    8.  
    9.             // notice how I use a reference to type (SData) here, you need this
    10.             // so that the returned object is converted into the correct type
    11.             fileData = (bData)DeserializeObject(data);
    12.  
    13.             Debug.LogError("Load(): Deserialized Data");
    14.  
    15.             //strings[0]=EncryptDecrypt(playerData.i.gamePlayTime);
    16.             //strings[1]=EncryptDecrypt(playerData.i.currentChapter);
    17.  
    18.             for (int i = 0; i < fileData.b.bdL.Count; i++) {
    19.  
    20.                 Debug.LogError("Load(): Loading Saved Blocks");
    21.  
    22.                 Debug.LogError("[SYSTEMBUILDMANAGER.BLOCKLOG]: Block List Line: " + i + ". Contains Block ID: " + fileData.b.bdL[i] + ". Saved at Position: " + fileData.b.bdX[i] + " X, " + fileData.b.bdY[i] + " Y.");
    23.  
    24.                 GameObject g = SystemPlaceableObjects.PlaceableObjects.PlaceableObjectPrefabs[int.Parse(fileData.b.bdL[i])];
    25.  
    26.                 float x = float.Parse(fileData.b.bdX[i]);
    27.  
    28.                 float y = float.Parse(fileData.b.bdY[i]);
    29.  
    30.                 Vector3 pos = new Vector3(x, y, 0.0F);
    31.  
    32.                 GameObject block = (GameObject)Instantiate(g, pos, Quaternion.identity);
    33.  
    34.             }
    35.  
    36. object DeserializeObject(string pXmlizedString) {
    37.         XmlSerializer xs = new XmlSerializer(typeof(bData));
    38.         MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));
    39.         //XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
    40.         return xs.Deserialize(memoryStream);
    41.     }
    The idea is the load function runs X times for each List entry, spawning a block with ID equal to the saved ID, at the X and Y positions
     
    Last edited: Jul 12, 2018
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Why in the world?

    Just use the Surrogate for Vector3 serialization, and leave the hard work to the serializer.
    You could just define the container, and store the following then:

    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class Container {
    4.     public List<Data> Data = new List<Data>();
    5.  
    6.    // Fill it
    7.    // Container container = new Container();
    8.    // container.Data.Add(new Data{ Id = ......
    9. }
    10.  
    11. [System.Serializable]
    12. public struct Data {
    13.     public string Id;
    14.     public Vector3 Pos;
    15. }
    16.  
    Here's an example on how to define Vector3 / Quaternion surrogate.
    https://answers.unity.com/questions/956047/serialize-quaternion-or-vector3.html

    Then just add the surrogate to the XMLSerializer, or use BinaryFormatter instead.

    You'll be able to push the whole container with all the data neccessary, plus get a use-ready list on load, instead of making really nasty workarounds.
     
    codevstudios likes this.