Search code examples
c#.netxmlserializationxsd2code

Serialize object to XML without empty elements and elements with empty children


I need to serialize object into XML request, but the service, I am calling, do not accept empty elements.

I generated contract classes from XSDs and the request structure looks like follows:

/* Generated contracts - simplified & without XML attributes */
public class Request
{
    public Request()
    {
        filters = new RequestFilters();
        properties = new ResultProperties();
    }

    public RequestFilters filters { get; set; }

    public ResultProperties properties { get; set; }
}

public class ResultProperties
{
    public ResultProperties()
    {
        props = new List<string>();
    }
    public List<string> props { get; set; }
}

public class RequestFilters
{
    public RequestFilters()
    {
        ids = new LongFilter();
        names = new StringFilter();
    }

    public LongFilter ids { get; set; }
    public StringFilter names { get; set; }
}

public class LongFilter
{
    public LongFilter()
    {
        values = new List<long>();
    }

    public List<long> values { get; set; }
}

public class StringFilter
{
    public StringFilter()
    {
        values = new List<string>();
    }

    public List<string> values { get; set; }
}

For the code generation I've used xsd2code and configured it to exclude empty collections from serialization. When creating object

Request request = new Request();

ResultProperties rp = new ResultProperties();
rp.props.Add("foo");
rp.props.Add("bar");

request.properties = rp;

I can serialize it into XML:

<?xml version="1.0" encoding="utf-16"?>
<Request xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <filters>
    <ids />
    <names />
  </filters>
  <properties>
    <props>
      <string>foo</string>
      <string>bar</string>
    </props>
  </properties>
</Request>

However the backend service complains about empty <ids /> and <names /> elements.

Is there a preferred/any way to exclude those empty elements from serialized XML?

If I manage to exclude <ids /> and <names />, eventually I would like to get rid of <filters> element too. Thanks.

UPDATE: Added relevant part of XSD

<xs:schema>
  <xs:element name="Request">
    <xs:complexType>
      <xs:sequence>             
        <xs:element name="filters" type="ouext:RequestFilters" minOccurs="0" />
        <xs:element name="properties" type="ouext:ResultProperties" minOccurs="0" />
      </xs:sequence>
    </xs:complexType>

    <xs:complexType name="RequestFilters">
      <xs:sequence>
        <xs:element name="ids" type="filters:LongFilter" minOccurs="0" />
        <xs:element name="names" type="filters:StringFilter" minOccurs="0" />
      <xs:sequence>
    </xs:complexType>

    <xs:complexType name="LongFilter">
      <xs:sequence>
        <xs:element name="values" type="filters:LongInFilter" minOccurs="0" />
      </xs:sequence>
    </xs:complexType>

    <xs:complexType name="LongInFilter">
      <xs:sequence>
        <xs:element name="value" type="xs:long" minOccurs="0" maxOccurs="20000" />
      </xs:sequence>
    </xs:complexType>

    <xs:complexType name="StringFilter">
      <xs:sequence>
        <xs:element name="values" type="filters:StringInFilter" minOccurs="0" />
      </xs:sequence>
    </xs:complexType>

    <xs:complexType name="StringInFilter">
      <xs:sequence>
        <xs:element name="value" type="xs:string" minOccurs="0" maxOccurs="20000" />
      </xs:sequence>
    </xs:complexType>

    <xs:complexType name="ResultProperties">
      <xs:sequence>
        <xs:element name="props" type="filters:StringInFilter" minOccurs="0" maxOccurs="unbounded">
        </xs:element>
      </xs:sequence>
    </xs:complexType>

  </xs:element>
</xs:schema>

UPDATE: Serialization is done as follows

string xmlPayload = null;
XmlSerializer serializer = new XmlSerializer(typeof(Request));
using (StringWriter sw = new StringWriter())
{
    using (XmlWriter xw = XmlWriter.Create(sw))
    {
        serializer.Serialize(xw, payload); // payload is initialized object of Request type
        xmlPayload = sw.ToString();
    }
}

Solution

  • I solved this by creating InitMethods, which null unused fields and collections.

    So instead of doing

    Request request = new Request();
    
    ResultProperties rp = new ResultProperties();
    rp.props.Add("foo");
    rp.props.Add("bar");
    
    request.properties = rp;
    

    I am doing

    Request InitRequest(RequestFilters filters = null, ResultProperties properties = null)
    {
         Request request = new Request();
         request.filters = filters;
         request.properties = properties;
    
         return request;
    }
    
    RequestFilters InitRequestFilters(LongFilter ids = null, StringFilter names = null)
    {
         RequestFilters filters = new RequestFilters();
         filters.ids = ids;
         filters.names = names;
    }
    

    And the same goes for initializing ResultProperties, LongFilter and StringFilter objects.

    Then I create request using InitMethods:

    List<string> properties = new List<string> { "foo", "bar" };
    ResultProperties rp = InitResultProperties(properties);
    
    Request request = InitRequest(properties: rp);
    

    And when I serialize the request, I get just the XML elements which contains actual values and empty elements are omitted from the serialization:

    <?xml version="1.0" encoding="utf-16"?>
    <Request xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <properties>
        <props>
          <string>foo</string>
          <string>bar</string>
        </props>
      </properties>
    </Request>
    

    It is not the optimal solution, because of the overhead of creating the InitMethods, but I didn't come up with anything better.