Search Unity

Answered: Help! Deserialising custom classes that contain lists of classes :!

Discussion in 'Scripting' started by Darhkuan, Jul 30, 2014.

  1. Darhkuan

    Darhkuan

    Joined:
    Feb 24, 2014
    Posts:
    24
    Hi all,

    I'm currently creating a simple multiplayer game using an authorative server approach (more of a learning curve for me than anything). I've hit a snag when it comes to passing a complex game object structure from the client to the server.

    I'm using XML Serialiser to send the xml structure over, and the structure is getting there in one piece and I can see from the debug log its held all its elements etc.
    My problem comes when I try to deserialise this into its original class. example class structure:


    Code (CSharp):
    1. [XmlRoot("Armies")]
    2. [System.Serializable]
    3. public class  Armies  {
    4.     public Army army = new Army();
    5.  
    6. public string armyName;
    7.  
    8.     [System.Serializable]
    9.     public class Army    {
    10. [XmlArray("regiments")]
    11.         [XmlArrayItem("Regiment")]
    12.         public List<Regiment> regiments = new List<Regiment>();
    13. [System.Serializable]
    14. public class Regiment
    15. {
    16. public string regimentName;
    17. }
    18. }
    19. }
    It goes through and gets the right fields for the first part (i.e. army section) but once it comes to the list of regiments it leaves it blank (empty), and just returns a collection of regiments of 0.

    From what I've read this is because it doesn't understand the type as it is serialised/deserialised so doesn't understand how to recreate the sub class??

    Would be very appreciative of any advice/guidance on:
    a) a better way to pass data structures for a multiplayer game, or
    b) if there is something i'm fundamentally doing wrong that could fix this issue? I've tried researching this but haven't been able to come to any solutions!

    I've included my serialiser code for reference:

    Code (CSharp):
    1.  public string SerializeXMLObject(Type myType, object pObject)
    2.     {
    3.         string XmlizedString = null;
    4.         MemoryStream memoryStream = new MemoryStream();
    5.         XmlSerializer xs = new XmlSerializer(myType);
    6.         XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
    7.         xs.Serialize(xmlTextWriter, pObject);
    8.         memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
    9.         XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());
    10.         return XmlizedString;
    11.        
    12.     }
    13.  
    14.     // Here we deserialize it back into its original form
    15.    public  object DeserializeObject(Type myType, string pXmlizedString)
    16.     {
    17.         XmlSerializer xs = new XmlSerializer(myType);
    18.         MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));
    19.                return xs.Deserialize(memoryStream);
    20.     }
     
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    And you did put instances of Regiments in your list... right?
     
  3. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    when dealing with plain text serialization under unity - i'd strongly suggest using a json.net implementation.

    there's a json.net imp on the app store that even works on ios.
     
  4. Darhkuan

    Darhkuan

    Joined:
    Feb 24, 2014
    Posts:
    24
    Hi LightStriker - little bit unsure what you are referring to here? Do you mean adding blank regiment instances before you try to assign the data? If so yes I have tried that.

    Code (CSharp):
    1. myArmy.army.regiments.Add(newReg);
    2. myArmy = (Armies)xmlSerialise.xmlSerial.DeserializeObject(typeof(Armies), _armyData);
    this still appears to come up blank with no data in the myArmy variable which is an instance of the class Armies
     
  5. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Annotate your Regiment class with [XmlType("Regiment")]

    Also you don't need the System.Serializable attribute unless you're also letting Unity serialize it.
     
  6. Darhkuan

    Darhkuan

    Joined:
    Feb 24, 2014
    Posts:
    24
    Hmm this still isn't working. either I am doing something fundamentally wrong or I still haven't got my head around this code. so to give a really basic example of the idea tell me if this should work, as I walk through an example with code from end to end:

    Basic XML File
    Code (CSharp):
    1. <armies>
    2.         <army>
    3.         <armyName>Army 1</armyName>
    4.               <regiments>
    5.                          <regiment>
    6.                          <regimentName>Regiment 1</regimentName>
    7.                          </regiment>
    8.                          <regiment>
    9.                          <regimentName>Regiment 2</regimentName>
    10.                          </regiment>
    11.               </regiments>
    12.         </army>
    13. </armies>
    Then we have the basic class definition within unity:
    Code (CSharp):
    1. [XmlRoot("armies")]
    2. public class armies {
    3.     public army myArmy = new army(); //declare instance of sub class army
    4.  
    5.     public class Army {
    6.         public string armyName;
    7.         [XmlArray("regiments")]
    8.             [XmlArrayItem("regiment")]
    9.             public List<regiment> regiments = new List<regiment>();
    10.  
    11.         [XmlType("regiment")]
    12.             public class regiment { public string regimentName;}
    13.     }
    14. }
    then using this load function to load into a string the xml file:

    Code (CSharp):
    1.    public string LoadFile(string _FileLocation, string FileName)
    2.     {
    3.         string data;
    4.         StreamReader r = File.OpenText(_FileLocation + FileName);
    5.         string _info = r.ReadToEnd();
    6.         r.Close();
    7.         data = _info;
    8.         Debug.Log("File Read");
    9.         return data;
    10.     }
    you can then run this code to serialise that xml data string, into an object of type "armies", populating all sub classes with the correct data

    Code (CSharp):
    1. armies ourArmyObject = new ourArmyObject();
    2. ourArmyObject = (armies)xmlSerialise.xmlSerial.DeserializeObject(typeof(armies), _armyData);
    and this calls out to the aforementioned deserialiser class which is the following:
    Code (CSharp):
    1. public  object DeserializeObject(Type myType, string pXmlizedString)
    2.     {
    3.         XmlSerializer xs = new XmlSerializer(myType);
    4.    
    5.         MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));
    6. return xs.Deserialize(memoryStream);
    7.     }
    The final result should be a completely populated armies object with all of its subclasses data.

    Please feel free to point out anything that doesn't look right, or if it does maybe I'm just trying to do something that cant be done and will need to look into the JSON implementation as mentioned by Frozen.
     
  7. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Your <army /> element doesn't map to anything. If you don't provide an XmlElement annotation then I think it'll default to the field name which would be <myArmy />
     
    Darhkuan likes this.
  8. Darhkuan

    Darhkuan

    Joined:
    Feb 24, 2014
    Posts:
    24
    WOOOO! I have finally got it working, a combination it would appear of me not quite understanding how to tag my class correctly and going back and working through a really basic example!

    Key takeaway for anyone following along is for the list, make sure you tag the XML Array & array item correctly, for the above example of regiment, use the following:
    Code (CSharp):
    1.     [XmlArray("regiments")]
    2.         [XmlArrayItem("regiment")]
    3.         public List<Regiment> regiments = new List<Regiment>();
    Please note that the list regiments is NOT what the XmlArray tag is referring to - that is actually the XmlTag in your xml files name. The ArrayItem is also referring to the XmlTag within your Xml file which defines each instance of the element.
    The next part which Kelso kindly pointed out is to make sure all your classes are Typed.
    Code (CSharp):
    1.  [XmlType("Regiment")]
    2.         public class Regiment
    This time the XmlType IS referring to the name of the class within your code.

    Hope that helps anyone else who runs across this issue and thanks heaps to everyone who responded in particular KelsoMRK!
     
  9. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Glad you got it working.

    It's worth noting that if regiments was a Regiment[] instead of a List<Regiment> then the XmlType annotation would not be necessary.