0

I'm receiving XML files with different layouts:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <PInstrument>
        <Type>myType</Type>
        <Data>
            <PB>blabla</PB>
            <MP>blabla</MP>
            <Mode>blabla</Mode>
        </Data>
    </PInstrument>
</Response>

or

<?xml version="1.0" encoding="UTF-8"?>
<Response>
    <PInstrument>
        <Type>myType</Type>
        <Data PB="blabla" MP="blabla" Mode="blabla" />
    </PInstrument>
</Response>

In XML both are perfectly correct, but here data contains either attributes or elements.

In .NET, to retrieve the XML inside an object, I must declare it but must specify whether this is an element or an attribute. As a matter of fact the XSD defines what's inside data as attributes so XSD created them as attributes.

But when I read the XML where they are elements, they simply don't populate as if the XmlDeserialize made a difference, though there's not in the standard as indicated in the W3C specs.

Why is .NET forcing elements to be elements and attributes to be attributes?

Is there a way to deserialize the file no matter the representation (attribute or element)?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
philippe
  • 78
  • 6
  • 2
    Xml Serialization is not meant for random XML file. The xml file for serialization to work must always be well structured. – jdweng Aug 31 '23 at 20:02

2 Answers2

1

I would use json for example

    using Newtonsoft.Json;

    var x = XElement.Parse(xml);

    var response =JObject.Parse(JsonConvert.SerializeXNode(x)
                .Replace("\"@", "\""))
                .ToObject<Root>();

c# classes

public class Root
{
    public Response Response { get; set; }
}

public class Data
{
    public string PB { get; set; }
    public string MP { get; set; }
    public string Mode { get; set; }
}

public class PInstrument
{
    public string Type { get; set; }
    public Data Data { get; set; }
}

public class Response
{
    public PInstrument PInstrument { get; set; }
}
Serge
  • 40,935
  • 4
  • 18
  • 45
  • That's interesting to consider using JSON to work with XML but there's a point here indeed. I'm going to investigate that – philippe Aug 31 '23 at 21:34
1

An attribute and child element of an XML element can have the same name, which may be one reason why .NET forces elements to be elements and attributes to be attributes.[1] I.e. the following is perfectly well-formed XML

<Data PB="foo">
  <PB>bar</PB>
</Data>

XmlSerializer allows you to bind to both the PB attribute and child element to distinct .NET properties.

In fact, this provides a workaround for your problem: you could create surrogate properties for each attribute that redirect the value of each to the property binding to the corresponding element, like so:

[XmlRoot("Data")]
public class Data
{
    [XmlElement("PB")] public string PB { get; set; }
    [XmlElement("MP")] public string MP { get; set; }
    [XmlElement("Mode")] public string Mode { get; set; }

    [XmlAttribute("PB")] public string PBAttribute { get => PB; set => PB = value; }
    [XmlAttribute("MP")] public string MPAttribute { get => MP; set => MP = value; }
    [XmlAttribute("Mode")] public string ModeAttribute { get => Mode; set => Mode = value; }

    // Prevent the elements from being re-serialized.
    public bool ShouldSerializePB() => false;
    public bool ShouldSerializeMP() => false;
    public bool ShouldSerializeMode() => false;
}

[XmlRoot("PInstrument")]
public class PInstrument 
{
    public string Type { get; set; }
    public Data Data { get; set; }
}

[XmlRoot("Response")]
public class Response 
{
    public PInstrument PInstrument { get; set; }
}

Demo fiddle here.,

This version of <Data> always re-serializes the values as attributes. If you need to track whether the values were deserialized as attributes or elements and re-serialize accordingly, you could use the {PropertyName}Specified pattern:

[XmlRoot("Data")]
public class Data
{
    [XmlElement("PB")] public string PB { get; set; }
    [XmlElement("MP")] public string MP { get; set; }
    [XmlElement("Mode")] public string Mode { get; set; }

    [XmlAttribute("PB")] public string PBAttribute { get => PB; set => PB = value; }
    [XmlAttribute("MP")] public string MPAttribute { get => MP; set => MP = value; }
    [XmlAttribute("Mode")] public string ModeAttribute { get => Mode; set => Mode = value; }

    [XmlIgnore] public bool PBSpecified { get; set; } = false; // Serialize as attributes unless specified.
    [XmlIgnore] public bool MPSpecified { get; set; } = false;
    [XmlIgnore] public bool ModeSpecified { get; set; } = false;

    [XmlIgnore] public bool PBAttributeSpecified { get => !PBSpecified; set => PBSpecified = !value; }
    [XmlIgnore] public bool MPAttributeSpecified { get => !MPSpecified; set => MPSpecified = !value; }
    [XmlIgnore] public bool ModeAttributeSpecified { get => !ModeSpecified; set => ModeSpecified = !value; }
}

Demo fiddle #2 here.


[1] Elements and attributes also appear differently in XSD schemas and have different restrictions on the types of values they can contain.

dbc
  • 104,963
  • 20
  • 228
  • 340