XmlSerializer for XML with hybrid elements

56 views Asked by At

I am trying to use XmlSerializer to serialize/deserialize SyncML. I am having difficulty with the pattern which occurs in the <Get> tag as shown below:

<SyncML xmlns="SYNCML:SYNCML1.2">
    <SyncHdr>
        <VerDTD>1.2</VerDTD>
        <VerProto>DM/1.2</VerProto>
        <!-- etc. -->
    </SyncHdr>
    <SyncBody>
        <Status>
            <CmdID>100</CmdID>
            <MsgRef>1</MsgRef>
            <CmdRef>0</CmdRef>
            <!-- etc. -->
        </Status>
        <Status>
            <CmdID>103</CmdID>
            <MsgRef>1</MsgRef>
            <CmdRef>4</CmdRef>
            <!-- etc. -->
        </Status>
        <Get>
            <CmdID>104</CmdID>
            <Item>
                <Target>
                    <!-- etc. -->
                </Target>
            </Item>
            <Item>
                <Target>
                    <!-- etc. -->
                </Target>
            </Item>
        </Get>
        <Get>
            <CmdID>105</CmdID>
            <Item>
                <Target>
                    <!-- etc. -->
                </Target>
            </Item>
            <Item>
                <Target>
                    <!-- etc. -->
                </Target>
            </Item>
        </Get>
        <Sequence>
            <CmdID>107</CmdID>
            <Replace>
                <CmdID>108</CmdID>
                <Item>
                    <Target>
                        <!-- etc. -->
                    </Target>
                </Item>
                <Item>
                    <Target>
                        <!-- etc. -->
                    </Target>
                </Item>
            </Replace>
            <Replace>
                <CmdID>109</CmdID>
                <Item>
                    <Target>
                        <!-- etc. -->
                    </Target>
                </Item>
            </Replace>
            <Get>
                <CmdID>110</CmdID>
                <Item>
                    <Target>
                        <!-- etc. -->
                    </Target>
                </Item>
            </Get>
        </Sequence>
        <Final/>
    </SyncBody>
</SyncML>

My classes so far are as follows:

[XmlRoot("SyncML", Namespace = "SYNCML:SYNCML1.2")]
public class SyncML
{
    [XmlElement]
    public SyncHdr SyncHdr { get; set; }

    [XmlArray("SyncBody")]
    [XmlArrayItem("Status", Type = typeof(StatusCommand))]
    [XmlArrayItem("Get", Type = typeof(GetCommand))]
    public SyncBody SyncBody { get; set; }
}

public class SyncHdr
{
    [XmlElement("VerDTD")]
    public string VerDtd { get; set; }

    [XmlElement("VerProto")]
    public string VerProto { get; set; }

    // etc.
}

public class SyncBody : List<SyncCommand>
{
}

public abstract class SyncCommand : List<Item>
{
    [XmlElement("CmdID")]
    public int CmdId { get; set; }
}

public class StatusCommand : SyncCommand
{
    [XmlElement("MsgRef")]
    public int MsgRef { get; set; }

    [XmlElement("CmdRef")]
    public int CmdRef { get; set; }

    // etc.
}

public class GetCommand : SyncCommand
{
    public List<Item> Items { get; set; }
}

public class Item
{
    [XmlElement("Target")]
    public Location Target { get; set; }
}

public class Location
{
    [XmlElement("LocURI")]
    public string LocUri { get; set; }
}

The problem is that in the XML, the Get tag contains one property element (CmdID) (as does the Status element, but also any number of Item elements. Is there a way to attribute up my GetCommand class to handle this? Do I need to compose my models differently?

1

There are 1 answers

0
dbc On BEST ANSWER

You basic problem is that, in two places, you are inheriting from List<T>. This is not recommended (see here for a discussion) and in addition is not well supported by serializers, e.g. properties of lists are never serialized. Instead, use classes that contain lists, and mark those lists with [XmlElement] to indicate they should be serialized without an outer wrapper element.

Thus your model should look something like:

[XmlRoot("SyncML", Namespace = "SYNCML:SYNCML1.2")]
public class SyncML
{
    [XmlElement]
    public SyncHdr SyncHdr { get; set; }

    [XmlArray("SyncBody")]
    [XmlArrayItem("Status", Type = typeof(StatusCommand))]
    [XmlArrayItem("Get", Type = typeof(GetCommand))]
    [XmlArrayItem("Final", Type = typeof(FinalCommand))]
    public List<SyncCommandBase> SyncBody { get; set; } = new ();
}

public class SyncHdr
{
    [XmlElement("VerDTD")]
    public string VerDtd { get; set; }

    [XmlElement("VerProto")]
    public string VerProto { get; set; }

    // etc.
}

public abstract class SyncCommandBase
{
}

public abstract class SyncCommand : SyncCommandBase
{
    [XmlElement("CmdID")]
    public int CmdId { get; set; }
}

public class StatusCommand : SyncCommand
{
    [XmlElement("MsgRef")]
    public int MsgRef { get; set; }

    [XmlElement("CmdRef")]
    public int CmdRef { get; set; }
}

public class GetCommand : SyncCommand
{
    [XmlElement(ElementName="Item")]
    public List<Item> Item { get; set; }
}

public class Item
{
    [XmlElement("Target")]
    public Location Target { get; set; }
}

public class Location
{
    [XmlElement("LocURI")]
    public string LocUri { get; set; }
}

public class FinalCommand : SyncCommandBase
{
}

Demo fiddle #1 here.

Notes that I inserted SyncCommandBase as an abstract superclass of SyncCommand and made FinalCommand inherit from it because the <Final/> element does not have a <CmdID> child element.

If you would prefer not to bind the <Final/> element into the SyncCommand list, you can modify your model as follows to make it an explicit property in an intermediate SyncBody class:

[XmlRoot("SyncML", Namespace = "SYNCML:SYNCML1.2")]
public class SyncML
{
    [XmlElement]
    public SyncHdr SyncHdr { get; set; } // Unchanged

    [XmlElement("SyncBody")]
    public SyncBody SyncBody { get; set; } = new ();
}

public class SyncBody
{
    [XmlElement("Status", Type = typeof(StatusCommand), Order = 1)]
    [XmlElement("Get", Type = typeof(GetCommand), Order = 1)]
    public List<SyncCommand> SyncCommands { get; set; } = new ();
    
    [XmlElement("Final", Order = 100)] // Force <Final> to come last when re-serializing
    public SyncFinal Final { get; set; }
}

public abstract class SyncCommand
{
    [XmlElement("CmdID")]
    public int CmdId { get; set; }
}

public class StatusCommand : SyncCommand
{
    [XmlElement("MsgRef")]
    public int MsgRef { get; set; }

    [XmlElement("CmdRef")]
    public int CmdRef { get; set; }
}

public class GetCommand : SyncCommand
{
    [XmlElement(ElementName="Item")]
    public List<Item> Item { get; set; }
}

public class Item
{
    [XmlElement("Target")]
    public Location Target { get; set; }
}

public class Location
{
    [XmlElement("LocURI")]
    public string LocUri { get; set; }
}

public class SyncFinal
{
}

Demo fiddle #2 here.