简体   繁体   中英

c# Deserialize with IXmlSerializable

I have xml like this:

    <data>    
  <audit_values>
    <audit_value>
      <channel>2</channel>
      <week>
        <mo_th>6,501698000000</mo_th>
        <fr>8,414278000000</fr>
        <sa>9,292674000000</sa>
        <sun>8,551982000000</sun>
        <holid>7,164605000000</holid>
      </week>
    </audit_value>
    <audit_value>
      <channel>1</channel>
      <week>
        <mo_th>6,501698000000</mo_th>
        <fr>8,414278000000</fr>
        <sa>9,292674000000</sa>
        <sun>8,551982000000</sun>
        <holid>7,164605000000</holid>
      </week>
    </audit_value>
  </audit_values>
</data>

And I need to deserialize it to class. But the problem is, that week will be change in future(it will be contains more elements, and name of them I dont know)

Data:

[XmlRoot("data")]
public class Data
{
    [XmlArray("audit_values")]
    [XmlArrayItem("audit_value", IsNullable = true)]
    public AuditValue[] AuditValues { get; set; }
}

AuditValue:

[XmlRoot("audit_value")]
public class AuditValue
{
    [XmlElement("week", typeof(TVR))]
    public Week Week;
}

Week:

    [XmlRoot("week")]
public class Week : IXmlSerializable
{
    public Dictionary<string, double> Values = new Dictionary<string, double>();

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.Read();
        var sub = reader.ReadSubtree();
        do
        {
            if (sub.NodeType == XmlNodeType.Element)
            {
                string name = sub.Name;
                string val = sub.ReadElementContentAsString();
                Values.Add(name, Convert.ToDouble(val));
            }
        } while (sub.Read());
    }

    public void WriteXml(XmlWriter writer)
    {

    }
}

But after deserialization I have only one element with only one recored in dictionary Values. What I'm doing wrong?

GitHub: https://github.com/olegletynain/XmlTest/tree/master/XmlTestRead

I tweaked your ReadXml method based on @Matthew Whited's idea in the comments section and the following method does the job:

public void ReadXml(XmlReader reader)
{
    Values = XElement.Parse(reader.ReadOuterXml())
        .Elements()
        .ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
}

As a side note you only need XmlRoot on the actual root element not on every class so I removed it from AuditValue and Week. Also I don't know what TVR is. It didn't compile with "typeof(TVR)" so I removed it as well.

For the sake of completeness here's my version of the classes:

[XmlRoot("data")]
public class Data
{
    [XmlArray("audit_values")]
    [XmlArrayItem("audit_value", IsNullable = true)]
    public AuditValue[] AuditValues { get; set; }
}

public class AuditValue
{
    [XmlElement("week")]
    public Week Week;
}

public class Week : IXmlSerializable
{
    public Dictionary<string, double> Values = new Dictionary<string, double>();

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        Values = XElement.Parse(reader.ReadOuterXml())
            .Elements()
            .ToDictionary(k => k.Name.ToString(), v => double.Parse(v.Value));
    }

    public void WriteXml(XmlWriter writer)
    {

    }
}

You should consider using the DataContractSerializer instead of XmlSerializer and implement the IExtensibleDataObject interface on your DataContract class.

Implementing IExtensibleDataObject allows the DataContract class to persist unknown information in the ExtensionData field, which prevents it from being lost if the XML being deserialized contains unknown elements, and then is re-serialized and saved.

Your Audit class would look something like this:

[DataContract(Name = "audit_value", Namespace = "")]
public class AuditValue : IExtensibleDataObject
{
    [DataMember(Name = "channel")]
    public int Channel { get; set; }

    [DataMember(Name = "week")]
    public Week Week { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

[DataContract(Name = "week", Namespace = "")]
public class Week : IExtensibleDataObject
{
    [DataMember(Name = "mo_th")]
    public Decimal MondayThroughThursday { get; set; }

    public ExtensionDataObject ExtensionData { get; set; }
}

You'll still need to update your code to deserialize the additional elements as POCOs, but at least the underlying data will be persisted until you get around to it.

Try this. XmlElement eliminates a lay of tags which you don't have. You have an extra 's' at end of values.

From 
    [XmlArray("audit_values")]
    [XmlArrayItem("audit_value", IsNullable = true)]
    public AuditValue[] AuditValues { get; set; }
​To
    [XmlElement("audit_value")]
    public AuditValue[] AuditValues { get; set; }

The error was in ReadXml method, now I changed it to this:

    public void ReadXml(XmlReader reader)
    {
        reader.Read();
        do
        {
            if (!reader.IsEmptyElement)
            {
                var name = reader.Name;
                var val = Convert.ToDouble(reader.ReadElementContentAsString());
                Values.Add(name, val);
            }
            else
            {
                reader.Skip();
            }
        } while (reader.Name != "week");
        if (reader.NodeType == XmlNodeType.EndElement)
        {
            reader.ReadEndElement();
        }
    }

And I works fine. This Solution without using XElement and Linq, @Volkan Paksoy offered method with XElement which is easier to understand

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM