How to Deserialize Xml with Dynamic Element Names

Deserialize XML into object with dynamic child elements

I've managed to resolve this using a dynamic type when deserializing.
When I deserialize ValuesRead, it is a defined as a dynamic type.

When deserialized, it turns into an XmlNode and from there I iterate over the node use the Name and InnerText values to read all the data.

How do you deserialize XML with incremented ID on the collection?

I can offer a custom XmlReader that will replace the specified element names on the fly.

public class ReplacingXmlReader : XmlTextReader
{
private readonly string _nameToReplace;

public ReplacingXmlReader(string url, string nameToReplace)
: base(url)
{
_nameToReplace = nameToReplace;
}

// Define the remaining constructors here.

public override string LocalName
{
get
{
if (base.LocalName.StartsWith(_nameToReplace))
return _nameToReplace;
return base.LocalName;
}
}
}

Usage is easy:

var xs = new XmlSerializer(typeof(Employees));

using var reader = new ReplacingXmlReader("test.xml", "EmployeeList");

var employees = (Employees)xs.Deserialize(reader);

If necessary, you can make a list of element names to replace.

Deserialize dynamic XML with generics C#

With Response<SoccerRoot>, the expected xml layout would be:

<RESPONSE>
<Data>
<SOCCER AnotherAttribute=...>

which does not match what you have. There is no way of customizing the element name of the T Data just using attributes.

Options:

  • have multiple root objects (one each for SOCCER, RESULTS, etc):

    [XmlRoot("RESPONSE")]
    public class SoccerRoot {
    [XmlElement("SOCCER")]
    public Soccer Soccer {get;set;}
    }
    [XmlRoot("RESPONSE")]
    public class ResultsRoot {
    [XmlElement("RESULTS")]
    public Results Results {get;set;}
    }
  • have a single root object that has multiple first-level children (one for SOCCER, one for RESULTS, etc - each of one type

    [XmlRoot("RESPONSE")]
    public class Response {
    [XmlElement("RESULTS")]
    public Results Results {get;set;}
    [XmlElement("SOCCER")]
    public Soccer Soccer {get;set;}
    }
  • use the generics approach, but lose the intermediate object so you have Response<Soccer> (delete SoccerRoot completely), and use XmlAttributeOverrides to customize the element name, being sure to cache the XmlSerializer generated (otherwise it will leak at an alarming rate)

Frankly, I'd go with either of the first two options and leave the third one alone.

Deserialize dynamic XML

Since you don't know what elements might be present in your Hit class, you can add a List<XElement> property to you class and attach the [XmlAnyElement] attribute to it. It will then capture any and all unknown elements in the XML for the class. Once the elements are deserialized, you can add API properties to query for elements with specific names, for instance:

public class Hit
{
// Since "id" is expected in the XML, deserialize it explicitly.
[XmlElement("id")]
public string Id { get; set; }

private readonly List<XElement> elements = new List<XElement>();

[XmlAnyElement]
public List<XElement> Elements { get { return elements; } }

#region convenience methods

public string this[XName name]
{
get
{
return Elements.Where(e => e.Name == name).Select(e => e.Value).FirstOrDefault();
}
set
{
var element = Elements.Where(e => e.Name == name).FirstOrDefault();
if (element == null)
Elements.Add(element = new XElement(name));
element.Value = value;
}
}

const string title = "Title";

[XmlIgnore]
public string Title
{
get
{
return this[title];
}
set
{
this[title] = value;
}
}

#endregion
}

Incidentally, you can eliminate your Hits class if you mark the Hits array with [XmlArray] rather than [XmlElement], like so:

[XmlRoot("SearchResponse")]
public sealed class SearchResponse
{
[XmlElement("Response", Type = typeof(Response))]
public Response[] Responses { get; set; }

[XmlElement("HitsCount", Type = typeof(HitsCount))]
public HitsCount[] HitsCount { get; set; }

[XmlArray("Hits")] // Indicates that the hits will be serialized with an outer container element named "Hits".
[XmlArrayItem("Hit")] // Indicates that each inner entry element will be named "Hit".
public Hit [] Hits { get; set; }
}

Update

The

    public string this[XName name] { get; set; }

Is an indexer. See Using Indexers (C# Programming Guide). I added it so it would be easy to do things like:

var description = hit["Description"];
var title = hit["Title"];

The indexer looks for the first XML element with the specified name, and returns its text value. If you don't want it, you can leave it out -- it's just for convenience.

c# XML serialization - dynamic element name

I think you should be able to use inheritance for this:

[XmlElement("Child", typeof(Child))]
[XmlElement("SpecialChild", typeof(SpecialChild))]
public List<Child> Childrens { get; set; }

with

public class SpecialChild : Child {...}

However: without inheritance, it isn't natively supported and you'd need to perform manual serialization.


Edit: confirmed, works fine:

using System;
using System.Collections.Generic;
using System.Xml.Serialization;
class Program
{ static void Main()
{
var ser = new XmlSerializer(typeof(Parent));
var obj = new Parent
{
Childrens = {
new Child { },
new SpecialChild { },
}
};
ser.Serialize(Console.Out, obj);
}
}
public class Parent
{
[XmlElement("Child", typeof(Child))]
[XmlElement("SpecialChild", typeof(SpecialChild))]
public List<Child> Childrens { get; } = new List<Child>();
}
public class Child { }
public class SpecialChild : Child { }

How to serialize an array to XML with dynamic tag names

Using XDocument :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication45
{
class Program
{

static void Main(string[] args)
{
string xmlIdent = "<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
"<GeneralInformation xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
"</GeneralInformation>";

XDocument doc = XDocument.Parse(xmlIdent);

XElement generalInfo = doc.Root;
XElement infoList = new XElement("InfoList");
generalInfo.Add(infoList);

for (int i = 0; i < 10; i++)
{
infoList.Add(new XElement("Infor" + i.ToString("0##"), new XElement("InfoName", "Test" + i.ToString("0##"))));
}

}

}
}

//<?xml version="1.0" encoding="utf-16"?>
//<GeneralInformation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
// <InfoList>
// <Info001>
// <InfoName>Test1</InfoName>
// </Info001>
// <Info002>
// <InfoName>Test2</InfoName>
// </Info002>
// <Info003>
// <InfoName>Test3</InfoName>
// </Info003>
// </InfoList>
//</GeneralInformation>

Deserialize nested XML with dynamic fields

@pratik directed me to the right way.
The correct code looks like following:

@JsonIgnoreProperties(ignoreUnknown=true)
@JacksonXmlRootElement(localName = "root")
public class BlogContent {
@JacksonXmlElementWrapper(useWrapping = false)
@JacksonXmlProperty(localName = "dynamic-element")
List dynElem;

//Constructor
...

//Getters
...

}

Important is to set the rootname with @JacksonXmlRootElement(localName = "root") and to set the JacksonXmlElementWrapper(useWrapping = false), because there is no wrapper in the xml !
@XmlRootElement(name = "root") works also!

Deserialize XML To Object using Dynamic

You may want to try this.

string xml = @"<Students>
<Student ID=""100"">
<Name>Arul</Name>
<Mark>90</Mark>
</Student>
<Student>
<Name>Arul2</Name>
<Mark>80</Mark>
</Student>
</Students>";

dynamic students = DynamicXml.Parse(xml);

var id = students.Student[0].ID;
var name1 = students.Student[1].Name;

foreach(var std in students.Student)
{
Console.WriteLine(std.Mark);
}

public class DynamicXml : DynamicObject
{
XElement _root;
private DynamicXml(XElement root)
{
_root = root;
}

public static DynamicXml Parse(string xmlString)
{
return new DynamicXml(XDocument.Parse(xmlString).Root);
}

public static DynamicXml Load(string filename)
{
return new DynamicXml(XDocument.Load(filename).Root);
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;

var att = _root.Attribute(binder.Name);
if (att != null)
{
result = att.Value;
return true;
}

var nodes = _root.Elements(binder.Name);
if (nodes.Count() > 1)
{
result = nodes.Select(n => n.HasElements ? (object)new DynamicXml(n) : n.Value).ToList();
return true;
}

var node = _root.Element(binder.Name);
if (node != null)
{
result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;
return true;
}

return true;
}
}

--EDIT--

To make it work with xml namespaces, I added RemoveNamespaces method.

public class DynamicXml : DynamicObject
{
XElement _root;
private DynamicXml(XElement root)
{
_root = root;
}

public static DynamicXml Parse(string xmlString)
{
return new DynamicXml(RemoveNamespaces(XDocument.Parse(xmlString).Root));
}

public static DynamicXml Load(string filename)
{
return new DynamicXml(RemoveNamespaces(XDocument.Load(filename).Root));
}

private static XElement RemoveNamespaces(XElement xElem)
{
var attrs = xElem.Attributes()
.Where(a => !a.IsNamespaceDeclaration)
.Select(a => new XAttribute(a.Name.LocalName, a.Value))
.ToList();

if (!xElem.HasElements)
{
XElement xElement = new XElement(xElem.Name.LocalName, attrs);
xElement.Value = xElem.Value;
return xElement;
}

var newXElem = new XElement(xElem.Name.LocalName, xElem.Elements().Select(e => RemoveNamespaces(e)));
newXElem.Add(attrs);
return newXElem;
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;

var att = _root.Attribute(binder.Name);
if (att != null)
{
result = att.Value;
return true;
}

var nodes = _root.Elements(binder.Name);
if (nodes.Count() > 1)
{
result = nodes.Select(n => n.HasElements ? (object)new DynamicXml(n) : n.Value).ToList();
return true;
}

var node = _root.Element(binder.Name);
if (node != null)
{
result = node.HasElements || node.HasAttributes ? (object)new DynamicXml(node) : node.Value;
return true;
}

return true;
}
}


Related Topics



Leave a reply



Submit