Search code examples
c#xmlserializationdeserializationxmlserializer

Xml Serialization Issues in Ordering and XMLRoot attributes


I'm writing a class to serialize a KiCAD BOM/Schematic file. The class works fine and correctly serialize and deserialize the original file but I'm having two small problems that I'm unable to fix. This's the class code:

[XmlRoot(ElementName = "export")]
public class SchematicExport
{
    [XmlAttribute]
    public const string version = "D";

    public Design design { get; set; }

    [XmlArrayItem(typeof(Component), ElementName = "comp")]
    public List<Component> components;

    [XmlArrayItem(typeof(LibPart), ElementName = "libpart")]
    public List<LibPart> libparts;

    [XmlArrayItem(typeof(Library), ElementName = "library")]
    public List<Library> libraries;

    [XmlArrayItem(typeof(Net), ElementName = "net")]
    public List<Net> nets;

    public class Design
    {
        public string source { get; set; }

        public string date
        {
            get => _date.ToString("dd/MM/yyyy HH:mm:ss");
            set => _date = DateTime.Parse(value);
        }

        private DateTime _date;

        public string tool { get; set; }

        [XmlElement("sheet")]
        public List<Sheet> sheets { get; }

        public class Sheet
        {
            [XmlAttribute]
            public int number { get; set; }

            [XmlAttribute]
            public string name { get; set; }

            [XmlAttribute]
            public string tstamps { get; set; }

            public TitleBlock title_block { get; set; }

            public class TitleBlock
            {
                public string title { get; set; }

                public string company { get; set; }

                public string rev { get; set; }

                public string date
                {
                    get => _date.ToString("yyyy-MM-dd");
                    set => _date = DateTime.Parse(value);
                }

                private DateTime _date;

                public string source { get; set; }

                [XmlElement("comment")]
                public List<Comment> comments;

                public class Comment
                {
                    [XmlAttribute]
                    public int number;

                    [XmlAttribute]
                    public string value;

                    public Comment()
                    {
                    }

                    public Comment(int number, string value) : this()
                    {
                        this.number = number;
                        this.value = value;
                    }
                }

                public TitleBlock()
                {
                    comments = new List<Comment>();
                    _date = DateTime.Now;
                }

                public TitleBlock(string title, string company, string rev, string date, string source)
                {
                    this.title = title;
                    this.company = company;
                    this.rev = rev;
                    this.date = date;
                    this.source = source;
                }
            }

            public Sheet()
            {
                title_block = new TitleBlock();
                tstamps = DateTime.Now.ToFileTime().ToString("X8");
            }

            public Sheet(int number, string name) : this()
            {
                this.number = number;
                this.name = name;
            }
        }

        public Design()
        {
            sheets = new List<Sheet>();
            _date = DateTime.Now;
        }

        public Design(string source, string date, string tool) : this()
        {
            this.source = source;
            this.date = date;
            this.tool = tool;
        }
    }

    public class Component
    {
        [XmlAttribute("ref")]
        public string reference { get; set; }
        public string value { get; set; }
        public string footprint { get; set; }
        public string datasheet { get; set; }

        [XmlArrayItem(typeof(Field), ElementName = "field")]
        public List<Field> fields;

        public LibSource libsource { get; set; }
        public SheetPath sheetpath { get; set; }
        public string tstamp { get; set; }

        public class LibSource
        {
            [XmlAttribute]
            public string lib { get; set; }
            [XmlAttribute]
            public string part { get; set; }
            [XmlAttribute]
            public string description { get; set; }
        }

        public class SheetPath
        {
            [XmlAttribute]
            public string names { get; set; }
            [XmlAttribute]
            public string tstamps { get; set; }
        }

        public Component()
        {
            fields = new List<Field>();
            libsource = new LibSource();
            sheetpath = new SheetPath();

            tstamp = DateTime.Now.ToFileTime().ToString("X8");
        }

        public Component(string reference, string value, string footprint) : this()
        {
            this.reference = reference;
            this.value = value;
            this.footprint = footprint;
        }
    }

    public class LibPart
    {
        [XmlAttribute]
        public string lib { get; set; }

        [XmlAttribute]
        public string part { get; set; }

        [XmlArrayItem(typeof(Field), ElementName = "field")]
        public List<Field> fields;

        [XmlArrayItem(typeof(string), ElementName = "fp")]
        public List<string> footprints;

        [XmlArrayItem(typeof(Pin), ElementName = "pin")]
        public List<Pin> pins;

        public class Pin
        {
            [XmlAttribute]
            public string num { get; set; }

            [XmlAttribute]
            public string name { get; set; }

            [XmlAttribute]
            public string type { get; set; }

            public Pin()
            {
            }

            public Pin(string num, string name, string type) : this()
            {
                this.num = num;
                this.name = name;
                this.type = type;
            }
        }

        public LibPart()
        {
            fields = new List<Field>();
            footprints = new List<string>();
            pins = new List<Pin>();
        }

        public LibPart(string lib, string part) : this()
        {
            this.lib = lib;
            this.part = part;
        }
    }

    public class Library
    {
        [XmlAttribute]
        public string logical { get; set; }

        public string uri { get; set; }

        public Library() { }

        public Library(string logical, string uri) : this()
        {
            this.logical = logical;
            this.uri = uri;
        }
    }

    public class Net
    {
        [XmlAttribute]
        public string code { get; set; }

        [XmlAttribute]
        public string name { get; set; }

        [XmlElement("node")]
        public List<Node> nodes;

        public class Node
        {
            [XmlAttribute("ref")]
            public string reference { get; set; }

            [XmlAttribute]
            public string pin { get; set; }

            public Node() { }

            public Node(string reference, string pin) : this()
            {
                this.reference = reference;
                this.pin = pin;
            }
        }

        public Net()
        {
            nodes = new List<Node>();
        }

        public Net(string code, string name) : this()
        {
            this.code = code;
            this.name = name;
        }
    }

    public class Field
    {
        [XmlAttribute]
        public string name { get; set; }

        [XmlText]
        public string value { get; set; }

        public Field()
        {
        }

        public Field(string name, string value) : this()
        {
            this.name = name;
            this.value = value;
        }
    }

    public SchematicExport()
    {
        design = new Design();
        components = new List<Component>();
        libparts = new List<LibPart>();
        libraries = new List<Library>();
        nets = new List<Net>();
    }

    public void Serialze(string filename)
    {
        XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport));
        TextWriter _textWriter = new StreamWriter(filename);
        _xmlSerializer.Serialize(_textWriter, this);
    }

    public static SchematicExport Create(string filename)
    {
        if (!System.IO.File.Exists(filename))
            throw new Exception("File not existing");

        SchematicExport _schematicExport = null;

        XmlSerializer _serializer = new XmlSerializer(typeof(SchematicExport));

        StreamReader _reader = new StreamReader(filename);
        _schematicExport = (SchematicExport)_serializer.Deserialize(_reader);

        return _schematicExport;
    }
}

The first issue is on the XmlRoot element for SchematicExport. I expected version attribute to appear inside the root XML tag but it does not:

<export xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

The second issue is the order of elements (OK it's not a big issue since tags are not order dependent) I tried to use XmlElement(Order=nn) attribute to force it but I just got a number of Reflection exceptions.

I just managed to remove xmlns:xsi and xmlns:xsd with a minor modification in serializer:

public void Serialze(string filename)
{
    XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport)); 
    XmlSerializerNamespaces _serializerNamespaces = new XmlSerializerNamespaces();

    _serializerNamespaces.Add("", "");

    using (TextWriter _textWriter = new StreamWriter(filename))
    {
        _xmlSerializer.Serialize(_textWriter, this, _serializerNamespaces);
    }
}

I found another (more malicious) issue, even if I can deserialize such Xml:

?xml version="1.0" encoding="UTF-8"?>
<export version="D">
  <design>
    <source>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119\V1-10A119.sch</source>
    <date>07/03/2019 10:33:01</date>
    <tool>Eeschema (5.0.2)-1</tool>
    <sheet number="1" name="/" tstamps="/">
      <title_block>
        <title>V1-10A119</title>
        <company>ROB.INT. s.r.l.</company>
        <rev>01</rev>
        <date>2019-02-25</date>
        <source>V1-10A119.sch</source>
        <comment number="1" value="Michele Santucci"/>
        <comment number="2" value=""/>
        <comment number="3" value=""/>
        <comment number="4" value=""/>
      </title_block>
    </sheet>
  </design>
  <components>
    <comp ref="U5">
      <value>ADM3101E</value>
      <footprint>Analog:LFCSP-12_EP_3x3_Pitch0.5mm</footprint>
      <datasheet>https://www.analog.com/media/en/technical-documentation/data-sheets/adm3101e.pdf</datasheet>
      <fields>
        <field name="Code">820-9369 </field>
        <field name="P/N">ADM3101EACPZ-250R7 </field>
        <field name="Productor">Analog</field>
        <field name="Provider">RS</field>
        <field name="manf#">ADM3101EACPZ-250R7 </field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="ADM3101E-analog" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C83E717</tstamp>
    </comp>
    <comp ref="C35">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AA07</tstamp>
    </comp>
    <comp ref="C37">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB0B</tstamp>
    </comp>
    <comp ref="C34">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB65</tstamp>
    </comp>
    <comp ref="C38">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AC22</tstamp>
    </comp>
    <comp ref="C36">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <datasheet>~</datasheet>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84D1BA</tstamp>
    </comp>
  </components>
  <libparts>
    <libpart lib="conn" part="CONN_2">
      <description>Symbole general de connecteur</description>
      <fields>
        <field name="Reference">P</field>
        <field name="Value">CONN_2</field>
      </fields>
      <pins>
        <pin num="1" name="P1" type="passive"/>
        <pin num="2" name="PM" type="passive"/>
      </pins>
    </libpart>
  </libparts>
  <libraries>
    <library logical="V1-10A119-rescue">
      <uri>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119/V1-10A119-rescue.lib</uri>
    </library>
    <library logical="conn">
      <uri>C:/Users/Public/Documents/Kicad/library/conn.lib</uri>
    </library>
  </libraries>
  <nets>
    <net code="200" name="VBUS">
      <node ref="P9" pin="2"/>
      <node ref="U3" pin="3"/>
      <node ref="C12" pin="2"/>
      <node ref="C30" pin="2"/>
      <node ref="U4" pin="3"/>
    </net>
    <net code="201" name="Net-(LD13-Pad1)">
      <node ref="LD13" pin="1"/>
      <node ref="R37" pin="1"/>
    </net>
  </nets>
</export>

The resulting serialization is not complete:

<?xml version="1.0" encoding="UTF-8"?>
<export version="D">
  <design>
    <source>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119\V1-10A119.sch</source>
    <date>07/03/2019 10:33:01</date>
    <tool>Eeschema (5.0.2)-1</tool>
    <sheet number="1" name="/" tstamps="/">
      <title_block>
        <title>V1-10A119</title>
        <company>ROB.INT. s.r.l.</company>
        <rev>01</rev>
        <date>2019-02-25</date>
        <source>V1-10A119.sch</source>
        <comment number="1" value="Michele Santucci"/>
        <comment number="2" value=""/>
        <comment number="3" value=""/>
        <comment number="4" value=""/>
      </title_block>
    </sheet>
  </design>
  <components>
    <comp ref="U5">
      <value>ADM3101E</value>
      <footprint>Analog:LFCSP-12_EP_3x3_Pitch0.5mm</footprint>
      <fields>
        <field name="Code">820-9369 </field>
        <field name="P/N">ADM3101EACPZ-250R7 </field>
        <field name="Productor">Analog</field>
        <field name="Provider">RS</field>
        <field name="manf#">ADM3101EACPZ-250R7 </field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="ADM3101E-analog" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C83E717</tstamp>
    </comp>
    <comp ref="C35">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AA07</tstamp>
    </comp>
    <comp ref="C37">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB0B</tstamp>
    </comp>
    <comp ref="C34">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AB65</tstamp>
    </comp>
    <comp ref="C38">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84AC22</tstamp>
    </comp>
    <comp ref="C36">
      <value>100nF</value>
      <footprint>Capacitors_SMD:C_0603</footprint>
      <fields>
        <field name="Code">135-8345 </field>
        <field name="P/N">06035G104ZAT2A</field>
        <field name="Productor">AVX</field>
        <field name="Provider">RS</field>
        <field name="manf#">06035G104ZAT2A</field>
      </fields>
      <libsource lib="V1-10A119-rescue" part="C_Small-Device" description=""/>
      <sheetpath names="/MICRO/" tstamps="/5D2A20EE/"/>
      <tstamp>5C84D1BA</tstamp>
    </comp>
  </components>
  <libparts>
    <libpart lib="conn" part="CONN_2">
      <fields>
        <field name="Reference">P</field>
        <field name="Value">CONN_2</field>
      </fields>
      <pins>
        <pin num="1" name="P1" type="passive"/>
        <pin num="2" name="PM" type="passive"/>
      </pins>
    </libpart>
  </libparts>
  <libraries>
    <library logical="V1-10A119-rescue">
      <uri>C:\Users\m.santucci\Documents\Progetti\Viper\Schede Viper\V1-10A119/V1-10A119-rescue.lib</uri>
    </library>
    <library logical="conn">
      <uri>C:/Users/Public/Documents/Kicad/library/conn.lib</uri>
    </library>
  </libraries>
  <nets>
    <net code="200" name="VBUS">
      <node ref="P9" pin="2"/>
      <node ref="U3" pin="3"/>
      <node ref="C12" pin="2"/>
      <node ref="C30" pin="2"/>
      <node ref="U4" pin="3"/>
    </net>
    <net code="201" name="Net-(LD13-Pad1)">
      <node ref="LD13" pin="1"/>
      <node ref="R37" pin="1"/>
    </net>
  </nets>
</export>

As you can see <datasheet> tags are missing (comp sections) as also <description> (libpart sections).

I cannot really understand where's the mistake, but apparently the problem is not a in deserialization:

Debug session


Solution

  • The first issue is on the XmlRoot element. I expected version attribute to appear inside the XMLRoot tag but it does not.

    As explained in Introducing XML Serialization, XmlSerializer will not serialize a const member even when public:

    XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information.

    <snip>

    XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.

    The easiest way to work around this is to add a surrogate property for the version, like so:

    public const string version = "D";
    
    [XmlAttribute("version")]
    [System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
    public string Version { get { return version; } set { /* Do nothing */ } }
    

    The property has to be public, but you can decrease its visibility via the attributes Browsable, EditorBrowsable and DebuggerBrowsable.

    The second issue is the order of elements (ok it's not a big issue since tags are not order dependent) I tried to use XmlElement(Order=nn) attribute to force it but I just got a number of Reflection exceptions.

    You didn't provide an example of this, but I was able to reproduce the following issue. If I set XmlArrayAttribute.Order on some but not all of the collection members of SchematicExport, I got the following exception:

    System.InvalidOperationException: There was an error reflecting type 'SchematicExport'. 
    ---> System.InvalidOperationException: Inconsistent sequencing: if used on one of the class's members, the 'Order' property is required on all particle-like members, please explicitly set 'Order' using XmlElement, XmlAnyElement or XmlArray custom attribute on class member 'nets'.
       at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
       at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
       at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
    

    Demo fiddle #1 here.

    The solution is to follow the advice in the exception message and apply the order attribute to all serializable members in the class.

    Thus SchematicExport should look something like:

    [XmlRoot(ElementName = "export")]
    public partial class SchematicExport
    {
        public const string version = "D";
    
        [XmlAttribute("version")]
        [System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
        public string Version { get { return version; } set { /* Do nothing */ } }
    
        [XmlElement(Order = 1)]
        public Design design { get; set; }
    
        [XmlArray(Order = 2)]
        [XmlArrayItem(typeof(Component), ElementName = "comp")]
        public List<Component> components;
    
        [XmlArray(Order = 3)]
        [XmlArrayItem(typeof(LibPart), ElementName = "libpart")]
        public List<LibPart> libparts;
    
        [XmlArray(Order = 4)]
        [XmlArrayItem(typeof(Library), ElementName = "library")]
        public List<Library> libraries;
    
        [XmlArray(Order = 5)]
        [XmlArrayItem(typeof(Net), ElementName = "net")]
        public List<Net> nets;
    }
    

    Note that setting the element order will not only reorder the elements during serialization, but also require that they be in that order during deserialization.

    Incidentally, in SchematicExport.Serialize(string) you need to close your StreamWriter. The easiest way to do this is via a using statement:

    public void Serialze(string filename)
    {
        XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport));
        // FIXED ensure the file is closed.
        using (var _textWriter = new StreamWriter(filename))
        {
            _xmlSerializer.Serialize(_textWriter, this);
        }
    }
    

    The first version of this method shown in your question does not do this.

    Demo fiddle #2 here.