XML array with custom attribute and element names - c#

I want to serialize/deserialize xml document in C# like:
<library>
<my.books genre =""classic"">
<book title = ""1984"" author=""George Orwell"" />
<book title = ""Robinson Crusoe"" author=""Daniel Defoe"" />
<book title = ""Frankenstein"" author=""Mary Shelly"" />
</my.books>
</library>";
There are 2 important things:
Element "my.books" must have custom name (not a property name)
my.books element must have an attribute ("genre").
Here is my code (sample is on https://dotnetfiddle.net/bH5WVX) :
using System;
using System.Xml;
using System.Xml.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;
using System.IO;
public class Program
{
public static void Main()
{
Library lib = new Library(myBooks: new MyBooks(
genre: "classic",
booklist: new List<Book>{
new Book("1984", "George Orwell"),
new Book("Robinson Crusoe", "Daniel Defoe"),
new Book("Oliver Twist", "Mary Shelly"),
}));
XmlSerializer formatter = new XmlSerializer(typeof(Library));
using (StringWriter sw = new StringWriter())
{
formatter.Serialize(sw, lib);
Console.Write(sw.ToString());
}
string desiredOutput =
#"<library>
<my.books genre =""classic"">
<book title = ""1984"" author=""George Orwell"" />
<book title = ""Robinson Crusoe"" author=""Daniel Defoe"" />
<book title = ""Frankenstein"" author=""Mary Shelly"" />
</my.books>
</library>";
}
[XmlRoot("library")]
public class Library
{
public MyBooks MyBooks { get; set; }
[XmlElement("my.books")]
public List<Book> Books { get; set; }
public Library()
{
}
public Library(MyBooks myBooks = null)
{
MyBooks = myBooks;
}
}
[XmlType("my.books")]
public class MyBooks
{
[XmlAttribute("genre")]
public string Genre { get; set; }
[XmlElement("book")]
public List<Book> Booklist { get; set; }
public MyBooks(string genre, List<Book> booklist = null)
{
Genre = genre;
Booklist = booklist;
}
public MyBooks()
{
}
}
public class Book
{
[XmlAttribute("title")]
public string Title { get; set; }
[XmlAttribute("author")]
public string Author { get; set; }
public Book() { }
public Book(string title, string author)
{
Title = title;
Author = author;
}
}
}
And the output is:
<library>
<MyBooks genre="classic">
<book title="1984" author="George Orwell" />
<book title="Robinson Crusoe" author="Daniel Defoe" />
<book title="Oliver Twist" author="Mary Shelly" />
</MyBooks>
</library>
The only problem is that I can't force element "MyBooks" to use name "my.books"
I found only one related article on this topic - http://www.codemeit.com/xml/c-xmlserializer-add-an-attribute-to-an-array-element.html, it suggests to use "XmlType" attribute on class, but it doesn't work here.
Is there any way to apply custom name attribute on this element?

It looks like your attribute was on the wrong property.
Try this:
[System.Xml.Serialization.XmlElement("my.books")]
public MyBooks MyBooks { get; set; }
public List<Book> Books { get; set; }
I now get this output:
<?xml version="1.0" encoding="utf-16"?>
<library xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<my.books genre="classic">
<book title="1984" author="George Orwell" />
<book title="Robinson Crusoe" author="Daniel Defoe" />
<book title="Oliver Twist" author="Mary Shelly" />
</my.books>
</library>
Well done on a superbly written question!

Related

Setting a XML Element Name and XML Attribute to a Class Property

I have this Class that I want to convert to an XML Element
public class Person
{
[XmlElement(ElementName = "PersonName")]
public string Name { get; set; }
}
This will display an XML
<Person>
<PersonName>Smith</PersonName>
</Person>
I want to add an attribute to the element PersonName
<Person>
<PersonName required="true">Smith</PersonName>
</Person>
How would I do that?
I think you need a special class to hold your Name property, rather than relying on string. Here is an example, using the XmlText and XmlAttribute attributes to control how the built-in XmlSerializer works:
using System.Xml.Serialization;
using System.IO;
namespace SomeNamespace
{
class Program
{
static void Main(string[] args)
{
Person me = new Person("me");
string path = "C:\\temp\\person.xml";
XmlSerializer serializer = new XmlSerializer(typeof(Person));
using (StreamWriter sw = new StreamWriter(path))
{
serializer.Serialize(sw, me);
}
}
}
public class Person
{
public Person() { } // needed for serialization
public Person(string name)
{
Name = new PersonName(name);
}
[XmlElement(ElementName = "PersonName")]
public PersonName Name { get; set; }
}
public class PersonName
{
public PersonName() { } // needed for serialization
public PersonName(string name)
{
Name = name;
}
[XmlText]
public string Name { get; set; }
[XmlAttribute] // serializes as an Attribute
public bool Required { get; set; } = true;
}
}
output (at C:\temp\person.xml; you can change Main to serialize to string and print to console if you want):
<?xml version="1.0" encoding="utf-8"?>
<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PersonName Required="true">me</PersonName>
</Person>
If you really want your Required attribute to be serialized as the lowercase "required", you can use different properties of XmlAttribute, such as: XmlAttribute(AttributeName = "required")

Why am I getting first node again on second iteration in this XDocument

I read about XML XDocument and running a few test but now I get the same first node on second lap in the foreach
Here´s my C# code for the CRUD get:
public IActionResult GetBookItems()
{
List<BookItem> BookItems = new List<BookItem>();
XDocument doc = _db.GetXmlDb();
foreach (XElement element in doc.Root.Descendants())
{
BookItem bookitem = new BookItem
{
/// Id
/// Author
/// Title
/// Genre
/// Price
/// Publish_date
/// Description
Id = element.Attribute("id").Value,
Author = element.Element("author").Value,
Title = element.Element("title").Value,
Genre = element.Element("genre").Value,
Price = element.Element("price").Value,
Publish_date = element.Element("publish_date").Value,
Description = element.Element("description").Value
};
BookItems.Add(bookitem);
BookItems = BookItems.OrderBy(p => p.Title).ToList();
}
return Ok(BookItems);
}
Here´s the xml databas
<?xml version="1.0"?>
<catalog>
<book id="B1">
<author>Kutner, Joe</author>
<title>Deploying with JRuby</title>
<genre>Computer</genre>
<price>33.00</price>
<publish_date>2012-08-15</publish_date>
<description>Deploying with JRuby is the missing link between enjoying JRuby and using it in the real world to build high-performance, scalable applications.</description>
</book>
<book id="B2">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen of the world.</description>
</book>
</catalog>
Also the program crash at second lap when doing new BookItem
System.NullReferenceException
HResult=0x80004003
Message=Object reference not set to an instance of an object.
Source=BooksProject
StackTrace:
at WorkSampleBookSearch.BooksXmlController.GetBookItems() in L:\NetProject\BooksProject\BooksProject\Controllers\BooksXmlController.cs:line 34
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
Descendants will get both the parent and all the child nodes of each element so you will get each element multiple times like book with child Author and then get Author a second time. See code below for solution :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
List<BookItem> bookitems = doc.Descendants("book").Select(x => new BookItem() {
Id = (string)x.Attribute("id"),
Author = (string)x.Element("author"),
Title = (string)x.Element("title"),
Genre = (string)x.Element("genre"),
Price = (decimal)x.Element("price"),
Publish_date = (DateTime)x.Element("publish_date"),
Description = (string)x.Element("description")
}).ToList();
}
}
public class BookItem
{
public string Id { get; set; }
public string Author { get; set; }
public string Title { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
public DateTime Publish_date { get; set; }
public string Description { get; set; }
}
}

C# Deserializing Atom XML

I want to deserialize the follwoing Atom XML:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:tel="http://tel.search.ch/api/spec/result/1.0/" xml:lang="de">
<id>https://tel.search.ch/api/ed14c7f5e42390640730cb18523640d5</id>
<title type="text">tel.search.ch API Search Results</title>
<generator version="1.0" uri="https://tel.search.ch">tel.search.ch</generator>
<updated>2018-08-28T02:00:00Z</updated>
<link href="https://tel.search.ch/result.html?was=0418553553" rel="alternate" type="text/html" />
<link href="http://tel.search.ch/api/?was=0418553553" type="application/atom+xml" rel="self" />
<openSearch:totalResults>1</openSearch:totalResults>
<openSearch:startIndex>1</openSearch:startIndex>
<openSearch:itemsPerPage>10</openSearch:itemsPerPage>
<openSearch:Query role="request" searchTerms="1234567890 " startPage="1" />
<openSearch:Image height="1" width="1" type="image/gif">https://www.search.ch/audit/CP/tel/de/api</openSearch:Image>
<entry>
<id>urn:uuid:ef812a3ea1e8e6f0</id>
<updated>2018-08-28T02:00:00Z</updated>
<published>2018-08-28T02:00:00Z</published>
<title type="text">Power, Max</title>
<content type="text">Max Power, Firststreet 1 6000 Switzerland/SZ 123 456 78 90</content>
<tel:nopromo>*</tel:nopromo>
<author>
<name>tel.search.ch</name>
</author>
<link href="https://tel.search.ch/switzerland/maxPower" title="Details" rel="alternate" type="text/html" />
<link href="https://tel.search.ch/switzerland/maxPower" type="text/x-vcard" title="VCard Download" rel="alternate" />
<link href="https://tel.search.ch/edit/?id=ef812a3ea1e8e6f0" rel="edit" type="text/html" />
</entry>
</feed>
Generatet by local.ch (Phonebook from Switzerland)
With this post (InvalidOperationException deserializing Atom XML) i created this tiny console application:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace consXMLtest
{
class Program
{
static void Main(string[] args)
{
XmlSerializer xs = new XmlSerializer(typeof(RssFeedModel));
XmlTextReader reader = new XmlTextReader(#"D:/test.xml");
RssFeedModel rssFeedModel = (RssFeedModel)xs.Deserialize(reader);
Console.WriteLine(rssFeedModel.Title);
Console.ReadKey();
}
}
}
[XmlRoot("feed", Namespace = "http://www.w3.org/2005/Atom")]
public class RssFeedModel
{
[XmlElement("title")]
public string Title { get; set; }
[XmlElement("link")]
public List<Link> link { get; set; }
[XmlElement("entry")]
public List<Entry> entry { get; set; }
}
[XmlRoot("link")]
public class Link
{
[XmlAttribute("rel")]
public string rel { get; set; }
[XmlAttribute("type")]
public string type { get; set; }
}
[XmlRoot("entry")]
public class Entry
{
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("published")]
public DateTime PublishDate { get; set; }
[XmlElement("content")]
public Content content { get; set; }
}
[XmlRoot("content")]
public class Content
{
[XmlAttribute("type")]
public string type { get; set; }
[XmlText]
public string text { get; set; }
}
Now my question: How can I access to the "Content" class?
For my application i only need the content.
Thank you for your help.
Greetings from Switzerland
niju
If I understand you correctly you simply iterate through all entries with
foreach(var c in rssFeedModel.entry)
Console.WriteLine(c.content.text);
and can read out the content properties.
Try this:
public static void Main()
{
XmlSerializer xs = new XmlSerializer(typeof(RssFeedModel));
XmlTextReader reader = new XmlTextReader(#"D:/test.xml");
RssFeedModel rssFeedModel = (RssFeedModel)xs.Deserialize(reader);
Console.WriteLine(rssFeedModel.entry.FirstOrDefault()?.content.text);
Console.ReadKey();
}

Deserializing an XML document with the root being a list

I have an XML document provided to me externally that I need to import into my application. The document is badly written, but not something I can really do much about.
<?xml version="1.0" encoding="iso-8859-1"?>
<xml>
<Items>
<Property1 />
<Property2 />
...
</Items>
<Items>
<Property1 />
<Property2 />
...
</Items>
...
</xml>
How should I go about using the XmlSerializer for this?
It doesn't seem to matter what class setup I use, or wether I put XmlRoot(ElementName="xml") on my base class it always says that the <xml xmlns=''> is unexpected.
Edit: Added the C# code I am using
[XmlRoot(ElementName = "xml")]
public class Container
{
public List<Items> Items { get; set; }
}
public class Items
{
public short S1 { get; set; }
public short S2 { get; set; }
public short S3 { get; set; }
public short S4 { get; set; }
public short S5 { get; set; }
public short S6 { get; set; }
public short S7 { get; set; }
}
public class XMLImport
{
public Container Data{ get; private set; }
public static XMLImport DeSerializeFromFile(string fileName)
{
XMLImport import = new XMLImport();
XmlSerializer serializer = new XmlSerializer(typeof(Container));
using (StreamReader reader = new StreamReader(fileName))
import.Data = (Container)serializer.Deserialize(reader);
return import;
}
}
Say you have a class Items maps to each <Items> node:
public class Items
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
You can deserialize your list of Items like this:
var doc = XDocument.Parse(
#"<?xml version=""1.0"" encoding=""iso-8859-1""?>
<xml>
<Items>
<Property1 />
<Property2 />
</Items>
<Items>
<Property1 />
<Property2 />
</Items>
</xml>");
var serializer = new XmlSerializer(typeof(List<Items>), new XmlRootAttribute("xml"));
List<Items> list = (List<Items>)serializer.Deserialize(doc.CreateReader());
The root of your XML is not a List, the root of your xml is the <xml> node I think you are just confused by its name :)
please visit the following link, It has many good answers voted by many people.
Here is the link: How to Deserialize XML document
Error Deserializing Xml to Object - xmlns='' was not expected
Simply take off the Namespace =:
[XmlRoot("xml"), XmlType("xml")]
public class RegisterAccountResponse {...}

Deserialize XML to Object Array

I'm trying to deserialize an XML file to an object array, but I'm receiving empty objects.
My question looks similar to this: How to Deserialize xml to an array of objects? but I can't seem to create a class which inherits IXmlSerializable. That said, I don't think that approach is necessary.
Am I doing something wrong?
File Object
[XmlType("file")]
public class File
{
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("company_name")]
public string Company_Name { get; set; }
[XmlElement("docs")]
public HashSet<doc> Docs { get; set; }
}
Doc Object
[XmlType("doc")]
public class Doc
{
[XmlElement("valA")]
public string ValA { get; set; }
[XmlElement("valB")]
public string ValB { get; set; }
}
XML
<?xml version="1.0" encoding="UTF-8"?>
<files>
<file>
<id>12345</id>
<company_name>Apple</company_name>
<docs>
<doc>
<valA>Info</valA>
<valB>More Info</valB>
</doc>
</docs>
</file>
<file>
<id>12345</id>
<company_name>Microsoft</company_name>
<docs>
<doc>
<valA>Even More Info</valA>
<valB>Lots of it</valB>
</doc>
</docs>
</file>
</files>
Deserialization code
XmlSerializer mySerializer = new XmlSerializer(typeof(File[]), new XmlRootAttribute("files"));
using (FileStream myFileStream = new FileStream("Files.xml", FileMode.Open))
{
File[] r;
r = (File[])mySerializer.Deserialize(myFileStream);
}
You have decorated your properties with XMLAttribute but they are elements in your XML. So, change all XMLAttribute to XmlElement.
[XmlType("file")]
public class File
{
[XmlElement("id")]
public string Id { get; set; }
[XmlElement("company_name")]
public string Company_Id { get; set; }
[XmlArray("docs")]
public HashSet<Doc> Docs { get; set; }
}
[XmlType("doc")]
public class Doc
{
[XmlElement("valA")]
public string ValA { get; set; }
[XmlElement("valB")]
public string ValB { get; set; }
}
Also you XML is not well formed. I guess this is typo though -
<company_name>Apple</company_id>
<company_name>Microsoft</company_id>
Ending tag should be company_name -
<company_name>Apple</company_name>
<company_name>Microsoft</company_name>
I would use xml parser..
XDocument doc=XDocument.Load(url);
File[] r=doc.Elements("file")
.Select(f=>
new File
{
Id=f.Element("id").Value,
Company_Id=f.Element("company_name").Value,
Docs=new HashSet<Docs>(
f.Elements("docs")
.Elements("doc")
.Select(d=>
new Doc
{
ValA=d.Element("valA").Value,
ValB=d.Element("valB").Value
}))
}).ToArray();

Categories