Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically choose what inherited properties will be serialized?

I have a base class with a few properties, and three deriving classes.

I want to serialize an object containing all three derived classes, but each derived class should expose a different set of properties from base class.

I want to do this dynamically with XmlAttributeOverrides and have tried a few differnt ways to do that, but nothing that really does it.

[Serializable]
public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
}

[Serializable]
public class B : A
{
}

[Serializable]
public class C : A
{
}

[Serializable]
public class Container
{
    public B B { get; set; }
    public C C { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        MemoryStream memoryStream = new MemoryStream();
        StreamWriter encodingWriter = new StreamWriter(memoryStream, Encoding.Unicode);

        var xmlWriter = XmlWriter.Create(encodingWriter, new XmlWriterSettings
        {
            Indent = false,
            OmitXmlDeclaration = true,
        });
        XmlAttributeOverrides overrides = new XmlAttributeOverrides();
        XmlAttributes attribute = new XmlAttributes();
        attribute.XmlIgnore = true;
        overrides.Add(typeof(B), "Property1", attribute);
        overrides.Add(typeof(C), "Property2", attribute);
        var container = new Container
            {
                B = new B {Property1 = "B property 1", Property2 = "B property 2"},
                C = new C {Property1 = "C property 1", Property2 = "C property 2"}
            };

        var xmlSerializer = new XmlSerializer(typeof(Container), overrides);
        xmlSerializer.Serialize(xmlWriter, container);

        var result = Encoding.Unicode.GetString(memoryStream.ToArray());
    }
}

In the above code, the result string will contain all the properties of A in B and C, but I really want it just to contain B Property2 and C Property1 (as I have set the XmlIgnore attributes for them).

How do I do this?

EDIT: The expected XML:

<Container xmlns:xsd="http://www.w3.org/2001/XMLSchema"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B><Property2>B property 2</Property2></B><C><Property1>C property 1</Property1></C></Container>

The actual XML:

<Container xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><B><Property1>B property 1</Property1><Property2>B property 2</Property2></B><C><Property1>C property 1</Property1><Property2>C property 2</Property2></C></Container>

EDIT 2: The above is an example to visualize the problem, but I will expand on why we have the need to do this.

We have a Container-class (like above) which contains different kinds of derived objects (like above). When we expose data from the Container-class to others, we want to be able to expose just certain fields which are configurable at some other place (might be sensitive data or whatnot).

This we do with help of XmlAttributeOverrides to set XmlIgnore property for the exposed properties. This works well for most types of objects (which does not have inheritance), but now we have the need to serialize different derived objects in different ways (configurable).

So in the above example, some customer has made the decision to exclude Property1 from class B and Property2 from class C, and as such I want the XML to look like above. This did not work with the above code though; it seems like XmlSerializer uses the settings for the properties from the base class A, instead of using it from the respective derived classes B and C.

like image 252
Joel Avatar asked Mar 20 '14 14:03

Joel


1 Answers

It's hard to tell from your question exactly what XML output you're looking for, so I'll just throw out an example and you can modify it as you need to. (EDIT: Seems I lucked out; the sample implementations below match your edited desired XML result)

You can use the little known ShouldSerializePROPERTYNAME method to dynamically instruct the XmlSerializer to ignore properties. For example:

public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    public virtual bool ShouldSerializeProperty1()
    {
        return true;
    }

    public virtual bool ShouldSerializeProperty2()
    {
        return true;
    }
}

These methods can then be overridden by the subclasses to ignore those properties:

public class B : A
{
    public override bool ShouldSerializeProperty1()
    {
        return false;
    }
}

public class C : A
{
    public override bool ShouldSerializeProperty2()
    {
        return false;
    }
}

Similarly, you can control the return values of the methods via other properties which can be assigned by Container:

public class A
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    [XmlIgnore]
    internal bool _ShouldSerializeProperty1 = true;
    [XmlIgnore]
    internal bool _ShouldSerializeProperty2 = true;

    public bool ShouldSerializeProperty1()
    {
        return _ShouldSerializeProperty1;
    }

    public bool ShouldSerializeProperty2()
    {
        return _ShouldSerializeProperty2;
    }
}

Then when assigning B and C to Container, you can set those flags:

public class Container
{
    private B _B;
    public B B 
    { 
        get
        {
            return _B;
        }
        set
        {
            if (value != null)
            {
                value._ShouldSerializeProperty1 = false;
                value._ShouldSerializeProperty2 = true;
            }
            _B = value;
        }
    }

    private C _C;
    public C C
    {
        get
        {
            return _C;
        }
        set
        {
            if (value != null)
            {
                value._ShouldSerializeProperty1 = true;
                value._ShouldSerializeProperty2 = false;
            }
            _C = value;
        }
    }
}

These are just some examples (and I don't claim to have used best practices here) to demonstrate how ShouldSerialize can be used. You'll probably want to adapt it however best to your particular usage.

EDIT: Given your updated post, there's another possibility but requires a bit of extra work defining your subclasses and some DRY violation (though for serialization, sometimes that's ok).

First define A's properties as virtual and override them in the subclasses as basic wrappers:

public class A
{
    public virtual string Property1 { get; set; }
    public virtual string Property2 { get; set; }
}

public class B : A
{
    public override string Property1 { get { return base.Property1; } set { base.Property1 = value; } }
    public override string Property2 { get { return base.Property2; } set { base.Property2 = value; } }
}

public class C : A
{
    public override string Property1 { get { return base.Property1; } set { base.Property1 = value; } }
    public override string Property2 { get { return base.Property2; } set { base.Property2 = value; } }
}

Then since (I'm assuming) you're managing/building your XmlSerializer serialization via those configurations, include XmlIgnore overrides for all of the base class's properties:

overrides.Add(typeof(A), "Property1", attribute);
overrides.Add(typeof(A), "Property2", attribute);

Then also include XmlIgnore overrides for the subclass properties that you wish to truly ignore:

overrides.Add(typeof(B), "Property2", attribute);
overrides.Add(typeof(C), "Property1", attribute);

This will produce your desired output.

like image 81
Chris Sinclair Avatar answered Sep 19 '22 03:09

Chris Sinclair