Search code examples
c#xml-serializationdatacontractserializer

During OnSerializing, able to avoid default values based on reference object, OnDeserialized fails


I have a reference object which has default values, I don't want to write these default values when I Serialize, this works perfect.

When I use the same logic during Deserialize, I get error. My goal is to avoid writing and reading default values.

Here is the example code that works.

this.parenttext = m_rp.parenttext;
//this.parenttext = "Default Value for Parent";

this.childtext = m_rp.Child[m_count].childtext;
//this.childtext = "Default Value for Child";

Here is the example code that does not work.

//this.parenttext = m_rp.parenttext;
this.parenttext = "Default Value for Parent";

//this.childtext = m_rp.Child[m_count].childtext;
this.childtext = "Default Value for Child";

Full code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Runtime.Serialization;

static class Module1
{


    public static void Main()
    {
        ReferenceParent rp = new ReferenceParent();
        PublicParent p = new PublicParent(rp);
        p.parenttext = "Default Value for Parent";
        p.Child[0].childtext = "Default Value for Child0";
        p.Child[1].childtext = "Default Value for Child1";

        p.parenttext = "Non-default Value for Parent";
        p.Child[0].childtext = "Non-default Value for Child0";
        p.Child[1].childtext = "Non-default Value for Child1";

        string xmlstring = null;

        XmlWriterSettings xmlws = new XmlWriterSettings();
        using (StringWriter sw = new StringWriter())
        {
            DataContractSerializer dcs1 = new DataContractSerializer(typeof(PublicParent));
            xmlws.Indent = true;
            xmlws.OmitXmlDeclaration = true;
            using (XmlWriter xwriter = XmlWriter.Create(sw, xmlws))
            {
                dcs1.WriteObject(xwriter, p);
                xwriter.Flush();
                xmlstring = sw.ToString();
            }
        }

        xmlstring = "<Parent xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/\">\r\n  <Child>\r\n    <Child />\r\n    <Child />\r\n  </Child>\r\n</Parent>";

        DataContractSerializer dcs2 = new DataContractSerializer(typeof(PublicParent));
        using (StringReader sr = new StringReader(xmlstring))
        {
            using (XmlReader xreader = XmlReader.Create(sr))
            {
                p = (PublicParent)dcs2.ReadObject(xreader);
            }
        }

    }

    [DataContract(Name = "Parent")]
    private class PublicParent
    {
        ReferenceParent m_rp;
        [DataMember(EmitDefaultValue = false)]
        private string m_parenttext;

        public string parenttext;
        [DataMember()]

        public PublicChild[] Child = new PublicChild[2];
        [OnSerializing()]
        private void OnSerializing(StreamingContext context)
        {
            if (this.parenttext == m_rp.parenttext)
             //if (this.parenttext == "Default Value for Parent")
            {
                this.m_parenttext = null;
            }
            else
            {
                this.m_parenttext = this.parenttext;
            }
        }

        [OnDeserialized()]
        private void OnDeserialized(StreamingContext context)
        {
            if (string.IsNullOrWhiteSpace(this.m_parenttext))
            {
                this.parenttext = m_rp.parenttext;
                //this.parenttext = "Default Value for Parent";
            }
            else
            {
                this.parenttext = this.m_parenttext;
            }
        }

        public PublicParent(ReferenceParent rp)
        {
            m_rp = rp;
            int count;
            for (count = 0; count <= 1; count++)
            {
                Child[count] = new PublicChild(rp, count);
            }
        }

        [DataContract(Name = "Child")]
        public class PublicChild
        {
            ReferenceParent m_rp;

            int m_count;
            [DataMember(EmitDefaultValue = false)]
            private string m_childtext;

            public string childtext;
            [OnSerializing()]
            private void OnSerializing(StreamingContext context)
            {
                if (this.childtext == m_rp.Child[m_count].childtext)
                //if (this.childtext == "Default Value for Child")
                {
                    this.m_childtext = null;
                }
                else
                {
                    this.m_childtext = this.childtext;
                }
            }

            [OnDeserialized()]
            private void OnDeserialized(StreamingContext context)
            {
                if (string.IsNullOrWhiteSpace(this.m_childtext))
                {
                    this.childtext = m_rp.Child[m_count].childtext;
                    //this.childtext = "Default Value for Child";
                }
                else
                {
                    this.childtext = this.m_childtext;
                }
            }

            public PublicChild(ReferenceParent rp, int count)
            {
                m_rp = rp;
                m_count = count;
            }
        }
    }

    [DataContract()]
    private class ReferenceParent
    {
        public string parenttext;
        [DataMember()]

        public ReferenceChild[] Child = new ReferenceChild[2];
        public ReferenceParent()
        {
            parenttext = "Default Value for Parent";
            int count;
            for (count = 0; count <= 1; count++)
            {
                Child[count] = new ReferenceChild();
                Child[count].childtext = "Default Value for Child" + count.ToString();
            }
        }

        [DataContract()]
        public class ReferenceChild
        {
            public string childtext;
        }
    }

}

Solution

  • From a quick run of the posted code, the thing that jumps out is that the sample xml code string you are running the deserialize against does not match the format of the serialized xml string.

    The output from serializing your instance gives:

    <Parent xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MyTypeNamespace">
      <Child>
        <Child />
        <Child />
      </Child>
    </Parent>
    

    (where MyTypeNamesapce is the c# namespace for the type)

    whereas the xml snippet you are running the deserialize against is

    <Parent xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/">
        <Child>
            <Child />
            <Child />
        </Child>
    </Parent>
    

    Note the differences in default namespace. By default, the DataContractSerializer will append the type's namespace to the default namesapce of http://schemas.datacontract.org/2004/07/.

    It may be that this is an artifact of you just posting a snippet of your code though. Are the namesapces consistent, and can you post the exception too?

    [EDIT - in response to comment from OP]

    Having commented out the lines you suggest, I too get a NullReferenceException.

    Your problem here is that the field PublicChild.m_rp is null when it is being deserialized. When you create the instances for serialization, you use the PublicChild(ReferenceParent rp, int count) constructor from the PublicParent(ReferenceParent rp) constructor. On Deserialization, these constructors won't have been called so the field will be null. You need to cater for that somehow; possibly by manually initialising these in your OnDeserialized.