Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize nullable type to optional non-nillable element

I have an xsd schema with an optional element (minOccurs=0, maxOccurs=1) of type int. The element is NOT defined as nillable. In the data model I would like to map this to a field of .net type Nullable<int>, where a null value should correspond to the element being omitted in the xml.

However, using the XmlSerializer, it seems I have to declare a nullable field in the datamodel with [XmlElement IsNullable=true]. If I set IsNullable=false, I get the exception "IsNullable may not be set to 'false' for a Nullable type."IsNullable may not be set to 'false' for a Nullable type. Consider using 'System.Int32' type or removing the IsNullable property from the XmlElement attribute." But if I understand correctly, setting IsNullable=true (or leaving the attribute out) is implicitly setting the element to nillable, and thereby changing the schema.

This is schema-first design, so I can't just add 'nillable' to the elements in the schema.

How do I map nullable .net types to non-nillable xml elements?

(I understand that I can omit nil-elements when serializing to xml by using XxxSpecified properties in the data model, but this approach still requires adding nillable to the xsd schema, as far as I can tell.)

Edit: Thanks to the comments I now understand the problem better. There is really two separate issues:

  1. Schema-to-code generators like xsd.exe creates a non-nullable type in the generated model if the schema element is non-nillable (even if it is optional). Can I override this (using any known code generator) so I get nullable types in the generated code?

  2. XmlSerializer requires nullable types in the data model to have [XmlElement IsNullable=true], which means the model implicitly adds 'nillable' to the schema. Can I avoid this?

like image 353
JacquesB Avatar asked Jan 08 '14 16:01

JacquesB


2 Answers

I also encountered with this problem some time ago. I solved it by introducing additional property that handles serialization logic.

  1. Firstly you mark your original property with [XmlIgnore] attribute to exlude it from serialization/deserialization.

    // Mark your original property from serialization/deserialization logic
    [XmlIgnore]
    public int? OriginalProperty { get; set; }
    
  2. Then add the additional property and mark it with [XmlElement("YourElementName")] attribute to handle serialization logic.

    // Move serialization logic to additional string property
    [XmlElement("OriginalPropertyXmlElement")]
    public string OriginalPropertyAsString
    {
        get
        {
            //...
        }
        set
        {
            //...
        }
    }
    
  3. On deserialization it will:

    set
    {
        // Check the field existence in xml
        if (string.IsNullOrEmpty(value))
        {
            // Set the original property to null
            this.OriginalProperty = default(int?);
        }
        else
        {
            // Get value from xml field
            this.OriginalProperty = int.Parse(value);
        }
    }
    
  4. On serialization:

    get
    {
        if (this.OriginalProperty.HasValue)
        {
            // Serialize it
            return this.OriginalProperty.ToString();
        }
        else
        {
            // Don't serialize it
            return null;
        }
    }
    
  5. The example could look like:

    [XmlRoot("scene")]
    public class Scene
    {
        [XmlIgnore]
        public int? ParentId { get; set; }
    
        [XmlElement("parent_id")]
        public string ParentIdAsString
        {
            get
            {
                return this.ParentId.HasValue ? this.ParentId.ToString() : null;
            }
    
            set
            {
                this.ParentId = !string.IsNullOrEmpty(value) ? int.Parse(value) : default(int?);
            }
        }
    }
    

It's pretty simple and you don't have to write your own serializer nor hacking xsd nor other stuff.

like image 178
Alexandr Nikitin Avatar answered Oct 14 '22 02:10

Alexandr Nikitin


I am not sure about XxxSpecified, but you can use the ShouldSerializeXxx methods. These happily work whether the property type is nullable or not. The following should do the job:

public int? Property { get ; set ; }

// this member is used for XML serialization
public bool ShouldSerializeProperty () { return Property.HasValue ; }

As for the generation of code from XSD schema, if you're using Microsoft's xsd.exe tool the best bet seems to be to post-process the generated assembly with e.g. Mono.Cecil to modify the types of the properties of interest and to insert any extra serialization-related members like ShouldSerializeXxx. Adding an XSLT pre-processing step to run xsd.exe on a 'fixed' schema with nillable element declarations achieves the first goal, but not the second. xsd.exe is not flexible enough to do what you want. You might also try adding this functionality to xsd2code, since it's open-source.

like image 24
Anton Tykhyy Avatar answered Oct 14 '22 02:10

Anton Tykhyy