3

I was wondering if you had any tips on how I can debug the below XML deserialization? I cannot get it to work. The deserializer basically creates the summon and slash instances, but all their properties are empty. The relevant classes to are shown below.


SkillCollection class with Deserializer:

[DataContract(Name = "Skills", Namespace = "")]
public class SkillCollection
{

[DataMember(Name = "Slash")]
public Skill Slash { get; set; }

[DataMember(Name = "Summon")]
public Skill Summon { get; set; }

public static object Deser(string path, Type toType)
{
    var s = new DataContractSerializer(toType);
    using (FileStream fs = File.Open(path, FileMode.Open))
    {
        object s2 = s.ReadObject(fs);
        if (s2 == null)
            Console.WriteLine(@"  Deserialized object is null");
        else
            Console.WriteLine(@"  Deserialized type: {0}", s2.GetType());
        return s2;
    }
}

It is called from another class through property Skills:

Skills = (SkillCollection)SkillCollection.Deser(
            Path.Combine(path, "Skills.xml"), 
            typeof(SkillCollection));

Skill class:

public class Skill
{
    //Cast: time it takes to cast it
    [DataMember(Name = "Cast")]
    public float Cast { get; set; }

    //ReCast: cooldown period before player can cast it again
    [DataMember(Name = "ReCast")]
    public float ReCast { get; set; }

    [DataMember(Name = "MPCost")]
    public int MpCost { get; set; }

    public Timer Timer { get; private set; }
    public bool Ready { get; set; }

    public Skill()
    {
        Ready = true;

        Timer = new Timer { Interval = ReCast + 500, AutoReset = false };
        Timer.Elapsed += OnTimedEvent;
    }

    //Runs when recast is up
    private void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        Ready = true;
    }
}

XML File:

<Skills>
  <Slash>
    <Cast>0.00</Cast>
    <ReCast>60.00</ReCast>
    <MPCost>0</MPCost>
  </Slash>
  <Summon>
    <Cast>5.98</Cast>
    <ReCast>2.49</ReCast>
    <MPCost>0</MPCost>
  </Summon>
</Skills>

Just so there is no confusion, my goal is to run the deserializer, and then have the SkillCollection class contain the two instances of Skill (Slash and Summon), and be able to access them separately through their properties.

Thanks for any help / tips with debugging this.

rene
  • 41,474
  • 78
  • 114
  • 152
Anders
  • 580
  • 8
  • 17
  • 1
    Add-on questions: 1) Does the order of the properties have to match the order in the XML? 2) What if there are not properties for all the blocks in the XML (say the XML had a third skill called kick, but kick did not have a property in SkillCollection). Would that cause an issue? 3) How about the other way around (there are properties that don't have blocks in the XML to match it). Would that be a problem? Thanks – Anders Sep 21 '14 at 16:16
  • 1
    I think the first step in debugging would be to run your object through the serializer and see what XML it produces. Then you can compare it the XML you've provided. However, `DataContractSerializer` would not be my choice for this. It has quite a few gotchas, for example [it requires the properties to be in alphabetical order in the xml](http://stackoverflow.com/questions/18463575/why-needs-datacontractserializer-alphabetically-sorted-xml). Use `XmlSerializer` instead. – Mike Zboray Sep 21 '14 at 17:27
  • 1
    @mikez Thanks!! Sorting by alpha and using the code Magnus provided sorted the issue. Thanks both of you! – Anders Sep 21 '14 at 18:15
  • 1
    @mikez The reason I used DataContractSerializer was that I could keep my properties' setters private. I found this helpful: http://web.archive.org/web/20130430190551/http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/ – Anders Sep 21 '14 at 18:19

1 Answers1

1

Anders,

I recently did a similar task. Looking at your code it seems you set DataContract Name and Namespace wrong. Both above the Skill class and the SkillCollection class you need to specify: [DataContract(Name = "Skills", Namespace = "")]. This is critical for the serializer to be "allowed" to enter the Properties. Keep in mind that just setting it above the SkillCollection class will not be enough.

EDIT: I initially thought you could leave out Namespace, but you cannot. It has to be set to either Namespace="", in the case you do not specify a namespace in the XML, or to whatever you specify the namespace to be, in the case you do specify it.

Also, a way to debug it is to change the Deser method to this:

    public static object Deserialize(string path, Type toType)
    {
        using (var sr = new FileStream(path, FileMode.Open))
        {
            SkillCollection p = null;
            XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(sr, new XmlDictionaryReaderQuotas());
            var serializer = new DataContractSerializer(toType);

            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        if (serializer.IsStartObject(reader))
                        {
                            Console.WriteLine(@"Found the element");
                            p = (SkillCollection)serializer.ReadObject(reader);
                            Console.WriteLine("{0} {1}    id:{2}",
                                p.Slash.ToString(), p.Summon.ToString());
                        }
                        Console.WriteLine(reader.Name);
                        break;
                }
            }

            return p;
        }
    }
Magnus
  • 6,791
  • 8
  • 53
  • 84
  • 1
    Thanks for the Edit. I tried leaving out Namespace as initially suggested, but using the debug code you suggested it was clear that it needed to be included. – Anders Sep 21 '14 at 18:12