Tony Stark
Tony Stark

Reputation: 2468

Deserialize different element types into one object Property in C#?

I would like to deserialize the following XML element.

<error code="1" type="post">
  <bad-request xmlns="blah:ns"> // <-- may be some different element name
    <text>You fail.</text> // <-- this is optional
  </bad-request>
</error>

The child element bad-request can have several different names. I would like to deserialize that element name to an error type which is defined as enumeration in my namespace like follows.

public enum ErrorType { BadRequest = 1, Forbidden = 2, Blah = 3, ... }

Additionally I would like to have the text' element text parsed into a ErrorText property. So my final class would look like that.

public class Error 
{
  public string ErrorText { get; set; }
  public ErrorType ErrorType { get; set; }
}

How do I achieve something like this in C# and with deserialization?

UPDATE:

My current solution seems like an overkill to me.

public class Error
{

    private string _type;

    [XmlAttribute("type")]
    public string Type // <-- there is clearly a bug in here I should not do that on the type attribute because it has nothing to do with the error type
    {
        get { return _type; }

        set
        {
            _type = value;

            switch (value)
            {
                case "bad-request":
                    ErrorType = ErrorTypes.BadRequest;
                    ErrorText = BadRequest.Text.Value;
                    break;                    
                default:
                    ...
                    break;
            }
        }
    }

    public ErrorTypes ErrorType { get; set; }
    public string ErrorText { get; set; }

    [XmlElement("bad-request")]
    public BadRequest BadRequest { get; set; }

}

public enum ErrorTypes 
{ 
    BadRequest = 0,
    Conflict = 1,
    FeatureNotImplemented = 2,
    Forbidden = 3
}

public class Text 
{
    [XmlText]
    public string Value { get; set; }
}

public class BadRequest
{        
    [XmlElement("text")]
    public Text Text { get; set; }
}

The code above would require to have a separate class for each element name/type.

Upvotes: 1

Views: 1839

Answers (1)

BartoszKP
BartoszKP

Reputation: 35891

You can deserialize such XML using inheritance instead of hiding it behind the enum:

public enum ErrorType { BadRequest = 1, Forbidden = 2, Blah = 3, ... }

[Serializable]
[XmlRoot(ElementName="error")]
public class Error
{
    [XmlElement(
        ElementName = "bad-request",
        Type = typeof(BadRequest),
        Namespace = "blah:ns")]
    [XmlElement(
        ElementName = "forbidden",
        Type = typeof(Forbidden),
        Namespace = "blah:ns")]
    public ErrorDetails ErrorDetails { get; set; }

    [XmlAttribute(AttributeName = "type")]
    public string Type { get; set; }

    [XmlAttribute(AttributeName = "code")]
    public int Code { get; set; }
}

[Serializable]
public abstract class ErrorDetails
{
  public abstract ErrorType ErrorType { get; }
}

[Serializable]
public class BadRequest : ErrorDetails
{
    public override ErrorType ErrorType
    {
      get
      {
        return ErrorType.BadRequest;
      }
    }

    [XmlElement(ElementName = "text")]
    public string Text { get; set; }

    public override string ToString()
    {
        return Text;
    }
}

[Serializable]
public class Forbidden : ErrorDetails
{
    public override ErrorType ErrorType
    {
      get
      {
        return ErrorType.Forbidden;
      }
    }

    [XmlElement(ElementName = "text")]
    public string Text { get; set; }

    public override string ToString()
    {
        return Text;
    }
}

Test:

string data1 = @"<error code=""1"" type=""post"">
  <bad-request xmlns=""blah:ns"">
    <text>You fail.</text>
  </bad-request>
</error>";
string data2 = @"<error code=""1"" type=""post"">
  <forbidden xmlns=""blah:ns"">
    <text>You fail.</text>
  </forbidden>
</error>";

byte[] bytes1 = ASCIIEncoding.ASCII.GetBytes(data1);
byte[] bytes2 = ASCIIEncoding.ASCII.GetBytes(data2);
MemoryStream ms1 = new MemoryStream(bytes1);
MemoryStream ms2 = new MemoryStream(bytes2);
XmlSerializer xs = new XmlSerializer(typeof(Error));

Error result1 = xs.Deserialize(ms1) as Error;
Error result2 = xs.Deserialize(ms2) as Error;
Console.WriteLine(result1.ErrorDetails.ToString());
Console.WriteLine(result2.ErrorDetails.ToString());

Upvotes: 3

Related Questions