Deserialize only specific item from XML and add to list - c#

I've got an XML file with multiple items, and I want to deserialize only a single specific one at a time, rather than all of them, and add it to a list.
Using the example from this site, how do I deserialize only the Product where Id=2 and add it to productList?
The Class:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
Code:
void foo()
{
string xmlString = "<Products><Product><Id>1</Id><Name>My XML product</Name></Product><Product><Id>2</Id><Name>My second product</Name></Product></Products>";
XmlSerializer serializer = new XmlSerializer(typeof(List<Product>), new XmlRootAttribute("Products"));
StringReader stringReader = new StringReader(xmlString);
List<Product> productList = (List<Product>)serializer.Deserialize(stringReader);
}

You can use the XDocument class to query Xml:
StringReader stringReader = new StringReader(xmlString);
XDocument document = XDocument.Load(stringReader);
var node = document.Descendants("Product").FirstOrDefault(p => p.Descendants("Id").First().Value == "2");
if(node != null)
{
XmlSerializer serializer = new XmlSerializer(typeof(Product));
var xmlReader = new StringReader(node.ToString());
Product result = serializer.Deserialize(xmlReader) as Product;
}
Granted, this is a quick and dirty solution that may require further analysis for certain situations.

Here this is working, you should Create XmlReader with your StringReader and read the subtree of the element. This can be achieved with ReadSubtree method.
public static void Main(string[] args)
{
string xmlString = "<Products><Product><Id>1</Id><Name>My XML product</Name></Product><Product><Id>2</Id><Name>My second product</Name></Product></Products>";
XmlSerializer serializer = new XmlSerializer(typeof(List<Product>), new XmlRootAttribute("Products"));
List<Product> productList = new List<Product>();
using (StringReader stringReader = new StringReader(xmlString))
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
xmlReader.ReadToDescendant("Products");
productList = (List<Product>)serializer.Deserialize(xmlReader.ReadSubtree());
}
}

Related

Generic XML parser for Poco Objects

Currently I try to write a generic XML parser and having trouble to write a generic Parser Class.
My current Parser:
public class XmlFileLoader
{
public T GetDeserializedData<T>(Type targetType, string rootElementName, string filename)
where T: class
{
XmlDocument doc = new XmlDocument();
XmlTextReader reader = new XmlTextReader(GetPath(filename));
reader.Read();
doc.Load(reader);
T result = DatabaseXmlSerializer.DeserializeXmlString<T>(doc.InnerXml, rootElementName, targetType);
return result;
}
}
My Deserializer:
public static class DatabaseXmlSerializer
{
public static T DeserializeXmlString<T>(string XmlString, string RootElementName , Type TargetType)
{
T tempObject = default;
using (MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(XmlString)))
{
XmlRootAttribute xRoot = new XmlRootAttribute();
xRoot.ElementName = RootElementName;
xRoot.IsNullable = true;
XmlSerializer xs = new XmlSerializer(TargetType, xRoot);
XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
tempObject = (T)xs.Deserialize(memoryStream);
}
return tempObject;
}
}
My call:
var loader= new XmlFileLoader();
var books = loader.GetDeserializedData<List<MySolution.Book>>(typeof(List<MySolution.Book>), "Bookstore", "Books.xml");
What is my concern?
I have to pass the type twice, but somehow i can't figure out how to just write it with one type.
I want my call to be like this:
var loader= new XmlFileLoader();
var books = loader.GetDeserializedData<List<MySolution.Book>>("Bookstore", "Books.xml");
There are so many errors in your code that I don't know what to do.
It's easier to rewrite the code completely.
public class XmlFileLoader
{
public T GetData<T>(string rootElementName, string filename)
{
var xmlSerializer = new XmlSerializer(typeof(T),
new XmlRootAttribute(rootElementName));
using (var xmlReader = XmlReader.Create(filename))
return (T)xmlSerializer.Deserialize(xmlReader);
}
}
This is the whole code!
Use it:
var xmlFileLoader = new XmlFileLoader();
var someModel = xmlFileLoader.GetData<SomeModel>("root", "filename");
The deserialization code is so simple that you can just throw out this class and just use XmlSerializer directly where you need it.
However, there is a serious problem when using XmlRootAttribute: multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. See documentation: Dynamically Generated Assemblies.
Therefore, it makes sense to cache serializer instances inside our class. Then its presence becomes justified.
public class XmlFileLoader
{
private static readonly Dictionary<(Type, string), XmlSerializer> serializers
= new Dictionary<(Type, string), XmlSerializer>();
public T GetData<T>(string rootElementName, string filename)
{
var key = (typeof(T), rootElementName);
if (!serializers.TryGetValue(key, out XmlSerializer xmlSerializer))
{
xmlSerializer = new XmlSerializer(typeof(T),
new XmlRootAttribute(rootElementName));
serializers.Add(key, xmlSerializer);
}
using (var xmlReader = XmlReader.Create(filename))
return (T)xmlSerializer.Deserialize(xmlReader);
}
}

Why my serialized file has the word "ArrayOf" in it [duplicate]

I am serializing List of objects List<TestObject>
, and XmlSerializer generates <ArrayOfTestObject> attribute, I want rename it or remove it.
Can it be done with creating new class that encapsulated List as field?
[XmlRoot("Container")]
public class TestObject
{
public TestObject() { }
public string Str { get; set; }
}
List<TestObject> tmpList = new List<TestObject>();
TestObject TestObj = new TestObject();
TestObj.Str = "Test";
TestObject TestObj2 = new TestObject();
TestObj2.Str = "xcvxc";
tmpList.Add(TestObj);
tmpList.Add(TestObj2);
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
XmlSerializer serializer = new XmlSerializer(typeof(List<TestObject>));
using (XmlWriter writer = XmlWriter.Create(#"C:\test.xml", settings))
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
serializer.Serialize(writer, tmpList, namespaces);
}
<ArrayOfTestObject>
<TestObject>
<Str>Test</Str>
</TestObject>
<TestObject>
<Str>xcvxc</Str>
</TestObject>
</ArrayOfTestObject>
The most reliable way is to declare an outermost DTO class:
[XmlRoot("myOuterElement")]
public class MyOuterMessage {
[XmlElement("item")]
public List<TestObject> Items {get;set;}
}
and serialize that (i.e. put your list into another object).
You can avoid a wrapper class, but I wouldn't:
class Program
{
static void Main()
{
XmlSerializer ser = new XmlSerializer(typeof(List<Foo>),
new XmlRootAttribute("Flibble"));
List<Foo> foos = new List<Foo> {
new Foo {Bar = "abc"},
new Foo {Bar = "def"}
};
ser.Serialize(Console.Out, foos);
}
}
public class Foo
{
public string Bar { get; set; }
}
The problem with this is that when you use custom attributes you need to be very careful to store and re-use the serializer, otherwise you get lots of dynamic assemblies loaded into memory. This is avoided if you just use the XmlSerializer(Type) constructor, as it caches this internally automatically.
Change the following line from:
XmlSerializer serializer = new XmlSerializer(typeof(List<TestObject>));
To:
XmlRootAttribute root = new XmlRootAttribute("TestObjects");
XmlSerializer serializer = new XmlSerializer(typeof(List<TestObject>), root);
It should work.
You can add an additional parameter to the XmlSerializer constructor to essentially name the root element.
XmlSerializer xsSubmit = new XmlSerializer(typeof(List<DropDownOption>), new XmlRootAttribute("DropDownOptions"));
This would result in the following structure:
<DropDownOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DropDownOption>
<ID>1</ID>
<Description>foo</Description>
</DropDownOption>
<DropDownOption>
<ID>2</ID>
<Description>bar</Description>
</DropDownOption>
</DropDownOptions>
Create another class like :
[XmlRoot("TestObjects")]
public class TestObjects: List<TestObject>
{
}
Then apply below code while sealizing :
XmlSerializer serializer = new XmlSerializer(typeof(TestObjects));
MemoryStream memStream = new MemoryStream();
serializer.Serialize(memStream, tmpList);

How to serialize to xml using Linq

How can i serialize Instance of College to XML using Linq?
class College
{
public string Name { get; set; }
public string Address { get; set; }
public List<Person> Persons { get; set; }
}
class Person
{
public string Gender { get; set; }
public string City { get; set; }
}
You can't serialize with LINQ. You can use XmlSerializer.
XmlSerializer serializer = new XmlSerializer(typeof(College));
// Create a FileStream to write with.
Stream writer = new FileStream(filename, FileMode.Create);
// Serialize the object, and close the TextWriter
serializer.Serialize(writer, i);
writer.Close();
Not sure why people are saying you can't serialize/deserialize with LINQ. Custom serialization is still serialization:
public static College Deserialize(XElement collegeXML)
{
return new College()
{
Name = (string)collegeXML.Element("Name"),
Address = (string)collegeXML.Element("Address"),
Persons = (from personXML in collegeXML.Element("Persons").Elements("Person")
select Person.Deserialize(personXML)).ToList()
}
}
public static XElement Serialize(College college)
{
return new XElement("College",
new XElement("Name", college.Name),
new XElement("Address", college.Address)
new XElement("Persons", (from p in college.Persons
select Person.Serialize(p)).ToList()));
);
Note, this probably isn't the greatest approach, but it's answering the question at least.
You can't use LINQ. Look at the below code as an example.
// This is the test class we want to
// serialize:
[Serializable()]
public class TestClass
{
private string someString;
public string SomeString
{
get { return someString; }
set { someString = value; }
}
private List<string> settings = new List<string>();
public List<string> Settings
{
get { return settings; }
set { settings = value; }
}
// These will be ignored
[NonSerialized()]
private int willBeIgnored1 = 1;
private int willBeIgnored2 = 1;
}
// Example code
// This example requires:
// using System.Xml.Serialization;
// using System.IO;
// Create a new instance of the test class
TestClass TestObj = new TestClass();
// Set some dummy values
TestObj.SomeString = "foo";
TestObj.Settings.Add("A");
TestObj.Settings.Add("B");
TestObj.Settings.Add("C");
#region Save the object
// Create a new XmlSerializer instance with the type of the test class
XmlSerializer SerializerObj = new XmlSerializer(typeof(TestClass));
// Create a new file stream to write the serialized object to a file
TextWriter WriteFileStream = new StreamWriter(#"C:\test.xml");
SerializerObj.Serialize(WriteFileStream, TestObj);
// Cleanup
WriteFileStream.Close();
#endregion
/*
The test.xml file will look like this:
<?xml version="1.0"?>
<TestClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SomeString>foo</SomeString>
<Settings>
<string>A</string>
<string>B</string>
<string>C</string>
</Settings>
</TestClass>
*/
#region Load the object
// Create a new file stream for reading the XML file
FileStream ReadFileStream = new FileStream(#"C:\test.xml", FileMode.Open, FileAccess.Read, FileShare.Read);
// Load the object saved above by using the Deserialize function
TestClass LoadedObj = (TestClass)SerializerObj.Deserialize(ReadFileStream);
// Cleanup
ReadFileStream.Close();
#endregion
// Test the new loaded object:
MessageBox.Show(LoadedObj.SomeString);
foreach (string Setting in LoadedObj.Settings)
MessageBox.Show(Setting);
you have to use the XML serialization
static public void SerializeToXML(College college)
{
XmlSerializer serializer = new XmlSerializer(typeof(college));
TextWriter textWriter = new StreamWriter(#"C:\college.xml");
serializer.Serialize(textWriter, college);
textWriter.Close();
}
You can use that if you needed XDocument object after serialization
DataClass dc = new DataClass();
XmlSerializer x = new XmlSerializer(typeof(DataClass));
MemoryStream ms = new MemoryStream();
x.Serialize(ms, dc);
ms.Seek(0, 0);
XDocument xDocument = XDocument.Load(ms); // Here it is!
I'm not sure if that is what you want, but to make an XML-Document out of this:
College coll = ...
XDocument doc = new XDocument(
new XElement("College",
new XElement("Name", coll.Name),
new XElement("Address", coll.Address),
new XElement("Persons", coll.Persons.Select(p =>
new XElement("Person",
new XElement("Gender", p.Gender),
new XElement("City", p.City)
)
)
)
);

Xml Serialization without XML Declaration [duplicate]

How do I serialize an XML-serializable object to an XML fragment (no XML declaration nor namespace references in the root element)?
Here is a hack-ish way to do it without having to load the entire output string into an XmlDocument:
using System;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
public class Example
{
public String Name { get; set; }
static void Main()
{
Example example = new Example { Name = "Foo" };
XmlSerializer serializer = new XmlSerializer(typeof(Example));
XmlSerializerNamespaces emptyNamespace = new XmlSerializerNamespaces();
emptyNamespace.Add(String.Empty, String.Empty);
StringBuilder output = new StringBuilder();
XmlWriter writer = XmlWriter.Create(output,
new XmlWriterSettings { OmitXmlDeclaration = true });
serializer.Serialize(writer, example, emptyNamespace);
Console.WriteLine(output.ToString());
}
}
You should be able to just serialize like you usually do, and then use the Root property from the resulting document.
You may need to clear the attributes of the element first.
By the way this is awesome.
I implemented this code to make it easy to work with xml fragments as classes quickly and then you can just replace the node when finished. This makes the transition between code and xml ultra-easy.
First create some extension methods.
public static class SerializableFragmentExtensions
{
public static XElement ToElement(this ISerializableFragment iSerializableFragment)
{
var serializer = new XmlSerializer(iSerializableFragment.GetType());
var emptyNamespace = new XmlSerializerNamespaces();
emptyNamespace.Add(String.Empty, String.Empty);
var output = new StringBuilder();
var writer = XmlWriter.Create(output,
new XmlWriterSettings { OmitXmlDeclaration = true });
serializer.Serialize(writer, iSerializableFragment, emptyNamespace);
return XElement.Parse(output.ToString(), LoadOptions.None);
}
public static T ToObject<T>(this XElement xElement)
{
var serializer = new XmlSerializer(typeof (T));
var reader = xElement.CreateReader();
var obj = (T) serializer.Deserialize(reader);
return obj;
}
}
Next Implement the required interface (marker interface--I know you are not supposed to but I think this is the perfect reason to it.)
public interface ISerializableFragment
{
}
Now all you have to do is decorate any Serializable class, you want to convert to an XElement Fragment, with the interface.
[Serializable]
public class SomeSerializableClass : ISerializableFragment
{
[XmlAttribute]
public string SomeData { get; set; }
}
Finally test the code.
static void Main(string[] args)
{
var someSerializableClassObj = new SomeSerializableClass() {SomeData = "Testing"};
var element = someSerializableClass.ToElement();
var backToSomeSerializableClassObj = element.ToObject<SomeSerializableClass>();
}
Thanks again for this amazingly useful code.

How to rename <ArrayOf> XML attribute that generated after serializing List of objects

I am serializing List of objects List<TestObject>
, and XmlSerializer generates <ArrayOfTestObject> attribute, I want rename it or remove it.
Can it be done with creating new class that encapsulated List as field?
[XmlRoot("Container")]
public class TestObject
{
public TestObject() { }
public string Str { get; set; }
}
List<TestObject> tmpList = new List<TestObject>();
TestObject TestObj = new TestObject();
TestObj.Str = "Test";
TestObject TestObj2 = new TestObject();
TestObj2.Str = "xcvxc";
tmpList.Add(TestObj);
tmpList.Add(TestObj2);
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
settings.Indent = true;
XmlSerializer serializer = new XmlSerializer(typeof(List<TestObject>));
using (XmlWriter writer = XmlWriter.Create(#"C:\test.xml", settings))
{
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
serializer.Serialize(writer, tmpList, namespaces);
}
<ArrayOfTestObject>
<TestObject>
<Str>Test</Str>
</TestObject>
<TestObject>
<Str>xcvxc</Str>
</TestObject>
</ArrayOfTestObject>
The most reliable way is to declare an outermost DTO class:
[XmlRoot("myOuterElement")]
public class MyOuterMessage {
[XmlElement("item")]
public List<TestObject> Items {get;set;}
}
and serialize that (i.e. put your list into another object).
You can avoid a wrapper class, but I wouldn't:
class Program
{
static void Main()
{
XmlSerializer ser = new XmlSerializer(typeof(List<Foo>),
new XmlRootAttribute("Flibble"));
List<Foo> foos = new List<Foo> {
new Foo {Bar = "abc"},
new Foo {Bar = "def"}
};
ser.Serialize(Console.Out, foos);
}
}
public class Foo
{
public string Bar { get; set; }
}
The problem with this is that when you use custom attributes you need to be very careful to store and re-use the serializer, otherwise you get lots of dynamic assemblies loaded into memory. This is avoided if you just use the XmlSerializer(Type) constructor, as it caches this internally automatically.
Change the following line from:
XmlSerializer serializer = new XmlSerializer(typeof(List<TestObject>));
To:
XmlRootAttribute root = new XmlRootAttribute("TestObjects");
XmlSerializer serializer = new XmlSerializer(typeof(List<TestObject>), root);
It should work.
You can add an additional parameter to the XmlSerializer constructor to essentially name the root element.
XmlSerializer xsSubmit = new XmlSerializer(typeof(List<DropDownOption>), new XmlRootAttribute("DropDownOptions"));
This would result in the following structure:
<DropDownOptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DropDownOption>
<ID>1</ID>
<Description>foo</Description>
</DropDownOption>
<DropDownOption>
<ID>2</ID>
<Description>bar</Description>
</DropDownOption>
</DropDownOptions>
Create another class like :
[XmlRoot("TestObjects")]
public class TestObjects: List<TestObject>
{
}
Then apply below code while sealizing :
XmlSerializer serializer = new XmlSerializer(typeof(TestObjects));
MemoryStream memStream = new MemoryStream();
serializer.Serialize(memStream, tmpList);

Categories

Resources