1

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?

Zenilogix
  • 1,318
  • 1
  • 15
  • 31

2 Answers2

1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
-2

The issue you're grappling with arises from the characteristic of SyncCommand being a List<Item>. Concurrently, GetCommand also encloses a List<Item> dubbed Items. Essentially, you're attempting to allocate Items twice for the GetCommand class, which is a misstep.

A prompt resolution to this predicament is to discard the List<Item> lineage from SyncCommand and formulate a List<Item> property within each of your GetCommand and StatusCommand classes. Observe that the Item list inside the StatusCommand class may not be required contingent on your XML structure.

Here is the revised code:

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

    [XmlElement("SyncBody")]
    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
{
    [XmlElement("Status", Type = typeof(StatusCommand))]
    [XmlElement("Get", Type = typeof(GetCommand))]
    public List<SyncCommand> Commands { 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; }

    // etc.
}

public class GetCommand : SyncCommand
{
    [XmlElement("Item")]
    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 principal alterations are the employment of [XmlElement("Status", Type = typeof(StatusCommand))] and [XmlElement("Get", Type = typeof(GetCommand))] to permit deserializing to varied types within the identical list, and shifting the List<Item> to each command type. Note the utilization of [XmlElement("Item")] as opposed to [XmlArrayItem("Item")] in GetCommand because you aspire each Item to be a direct offspring of Get, not in an Items parent tag.

  • 1
    This answer looks like ChatGPT – DavidW Jul 23 '23 at 05:53
  • [Another answer](https://stackoverflow.com/questions/76744618/typescript-stage-3-fields-decorators/76744632#76744632) had a slip-up: *"At the epoch of my knowledge cutoff in September 2021"*. – Peter Mortensen Jul 23 '23 at 09:51