How to deserialize an unknown element using XmlSerializer? [duplicate] - c#

I have a class that is serialized into/deserialized from XML and stored in/restored from a file:
public class Customer
{
public string FirstName;
public string LastName;
public Customer()
{
}
public Customer(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public static Customer Load(TextReader reader)
{
XmlSerializer deserializer = new XmlSerializer(typeof(Customer));
return (Customer)deserializer.Deserialize(reader);
}
public void Save(TextWriter writer)
{
XmlSerializer serializer = new XmlSerializer(GetType());
serializer.Serialize(writer, this);
}
}
In a newer version of this class I added a new property
public string MiddleName;
It is a common use case that a user has installed both the old and new version of my program. They both read and write the same serialized file. When the new version writes the file, all three properties (FirstName, LastName, MiddleName) are written. The old program reads the file but omits the unknown element MiddleName. It saves the file without MiddleName, so it's value is lost for the newer program.
Is there a way to store the original XML when deserializing and 'merge' the unknown elements back in when serializing? The old program would ignore unknown elements but write them back into the file so they are not lost for the new program.

Can't test it right now as I'm on Mac OS X, but XmlAnyElement should work:
[XmlAnyElement]
public XmlElement[] Unsupported { get; set; }

XMLSerializer serializes. If the info is not available how could it serialize that?
What you could do is update the xmlfile, instead of serializing and overwriting it. It is extra work because you will have to traverse the file yourself but it allows you to achieve what you want.
How To Modify and Save XML with the XmlDocument Class in the .NET Framework SDK

Related

serialize/deserialize xml with uknownw elements [duplicate]

I have a class that is serialized into/deserialized from XML and stored in/restored from a file:
public class Customer
{
public string FirstName;
public string LastName;
public Customer()
{
}
public Customer(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public static Customer Load(TextReader reader)
{
XmlSerializer deserializer = new XmlSerializer(typeof(Customer));
return (Customer)deserializer.Deserialize(reader);
}
public void Save(TextWriter writer)
{
XmlSerializer serializer = new XmlSerializer(GetType());
serializer.Serialize(writer, this);
}
}
In a newer version of this class I added a new property
public string MiddleName;
It is a common use case that a user has installed both the old and new version of my program. They both read and write the same serialized file. When the new version writes the file, all three properties (FirstName, LastName, MiddleName) are written. The old program reads the file but omits the unknown element MiddleName. It saves the file without MiddleName, so it's value is lost for the newer program.
Is there a way to store the original XML when deserializing and 'merge' the unknown elements back in when serializing? The old program would ignore unknown elements but write them back into the file so they are not lost for the new program.
Can't test it right now as I'm on Mac OS X, but XmlAnyElement should work:
[XmlAnyElement]
public XmlElement[] Unsupported { get; set; }
XMLSerializer serializes. If the info is not available how could it serialize that?
What you could do is update the xmlfile, instead of serializing and overwriting it. It is extra work because you will have to traverse the file yourself but it allows you to achieve what you want.
How To Modify and Save XML with the XmlDocument Class in the .NET Framework SDK

Handling deserialize error when xml file has incorrect data

I wrote a basic settings storage class for user inputs that loads and saves data from a xml file. It works great when the xml has all the correct values.
However one of the values is an enum that has had some of it's accepted values changed in version updates. Because of this some users have xml settings files that are no longer compatible with the latest version.
Because I know all the possible previous values for this input, is it possible that when the deserializer encounters one of those values, it reads it as a different value?
public class UserInput
{
//... long list of variables
//enum that had its values changed from prior versions but still has the same name
public MyEnum UserType { get; set; }
private static XmlSerializer xs;
static UserInput()
{
xs = new XmlSerializer(typeof(UserInput));
}
public void SaveToFile(string fileName)
{
using (StreamWriter sr = new StreamWriter(fileName))
{
xs.Serialize(sr, this);
}
}
public static UserInput ReadFromFile(string fileName)
{
using (StreamReader sr = new StreamReader(fileName))
{
return xs.Deserialize(sr) as UserInput;
}
}
}
I'm hoping I can insert a check into the above code so that when my enum that has changed is encountered it can load it as a valid value.

Process XML output from API

I am getting an XML return from an Ebay API call. This is actually an Ebay category list of collections. But the problem is, I can't access its collection from XML output. I have attached two pictures - the first one showing debug of XML value returning variable, and the second one showing "InnerList". My main goal is prepare this XML data to store on my database, so I need a clean list of values from XML data. Any ideas?
You could deserialize your xml into your own class/object - Then it might be easier to work with. All i do is put xml tags to a class and i can deserialize it. See the class and method below:
public static T Deserialize<T>(string xmlText)
{
try
{
var stringReader = new System.IO.StringReader(xmlText);
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
catch
{
throw;
}
}
[XmlElement("adress")]
public class Adress
{
[XmlElementAttribute("street_address")]
public string street_address { get; set; }
[XmlElementAttribute("postal_code")]
public string postal_code { get; set; }
[XmlElementAttribute("city")]
public string city { get; set; }
[XmlElementAttribute("country")]
public string country { get; set; }
}
public main()
{
Adress myAdress = Deserialize<Adress>(XMLstring);
}
Hope it helps!
It seems you are using Ebay SDK. Please try code below to process return values.
foreach (CategoryTypeCollection item in categories)
{
item.ItemAt(0).CategoryID = "This is how you access the properties of he returned result";
// THE XML is already parsed for you via SDK, so you don't have to parse it...
// since i wrote foreach loop here, always access itemAt 0th index posiiton
}

How can I loop through the JSON on Universal Windows Platform with C#

I am developing an app on UWP.
When I connect with a server api and I get the next response I don't have problems.
{"value":"Login successfull","sessionId":"a95077855b05ed0fec5d7fa3abafa126e15aba2a"}
I can get information in the following way:
JsonObject jsonObject = JsonObject.Parse(jsonString);
string token = jsonObject["sessionId"].GetString();
string value = jsonObject["value"].GetString();
but my problem is when i get the next response of the api:
[{"person":{"name":"name1","country":"Spain","city":"user_city","phone":null}},{"person":{"name":"name2","country":"Turkey","city":"user_city","phone":"1111111"}},{"person":{"name":"name3","country":"Argentina","city":"user_city","phone":"22222"}},{"person":{"name":"name4","country":"Argentina","city":"user_city","phone":"33333"}}]
How can I loop through the JSON and get all the people that match a condition?
I have to do with "Windows.Data.Json"
If interested in a solution using only Windows.Data.Json namespace, here it is:
var rootValue = JsonValue.Parse(jsonString);
foreach (var item in rootValue.GetArray())
{
var unamedObject = item.GetObject();
var personObject = unamedObject["person"].GetObject();
System.Diagnostics.Debug.WriteLine(personObject["name"].GetString());
System.Diagnostics.Debug.WriteLine(personObject["country"].GetString());
System.Diagnostics.Debug.WriteLine(personObject["city"].GetString());
System.Diagnostics.Debug.WriteLine(personObject["phone"].GetString());
}
Why would somebody pick Windows.Data.Json over Newtonsoft's Json.net?
If your JSON needs are simple, you can reduce the size of your app ~1 MB by choosing Windows.Data.Json because it is part of the operating system.
I would recommend you try out Json.net nuget package and deserialise the json payload to classes through that.
A good tutorial can be found here: http://windowsapptutorials.com/windows-phone/general/deserialize-json-data-using-newtonsoft-json-net-library/
But if you search you'll find more.
In short, you first copy paste your json and use Visual Studio > File > Paste Special > To paste to classes ( first open an empty cs file and set your cursor inside it ).
After that you use JsonConvert.DeserializeObject<RootObject>() to actually parse the json string.
Once parsed you'll have an array of items if your original json also defined an array.
Note RootObject is the first class object in the generated classes in Visual Studio
There are ways to do it without external libraries, if that is the real reason for the stipulation of Windows.Data.Json.
I'd likely do it something like this...
First I'd make some classes representing the returning JSON:
public class RootObject
{
public Person person { get; set; }
}
public class Person
{
public string name { get; set; }
public string country { get; set; }
public string city { get; set; }
public string phone { get; set; }
}
Then add a little method to deserialize:
public static T Deserialize<T>(string json)
{
var bytes = Encoding.Unicode.GetBytes(json);
using (var ms = new MemoryStream(bytes))
{
var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(ms);
}
}
And finally deserialize and query that result like so:
var persons = Json.Deserialize<List<RootObject>>(textBox.Text);
var peeps = (from p in persons
where p.person.name.StartsWith("name")
select p).ToList();

Deserialization of serialized data fails

I am trying to deserialize an XML document that I am also serializing at another time. I am using it to store a configuration file.
This is my Code:
namespace OrderTracker
{
[Serializable]
public class AutofillValues
{
private string fileName = Directory.GetCurrentDirectory() + "\\bin\\settings.db";
public ComboBox.ObjectCollection Vendors { get; set; }
public ComboBox.ObjectCollection Products { get; set; }
public ComboBox.ObjectCollection Companies { get; set; }
public void save(AutofillValues afv)
{
if (!File.Exists(fileName))
{
FileStream fs = File.Create(fileName);
fs.Close();
}
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
TextWriter writer = new StreamWriter(fileName);
x.Serialize(writer, afv);
writer.Close();
}
public AutofillValues load()
{
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
TextReader file = new StreamReader(fileName);
AutofillValues av = (AutofillValues)x.Deserialize(file);
file.Close();
return av;
}
}
}
The error message that I am getting when trying to deserialize the file is this;
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.dll
Additional information: There is an error in XML document (2, 2).*
This is the XML document:
<?xml version="1.0" encoding="utf-8"?>
<AutofillValues xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Vendors>
<anyType xsi:type="xsd:string">Test Vendor</anyType>
</Vendors>
<Products>
<anyType xsi:type="xsd:string">Test Product</anyType>
</Products>
<Companies>
<anyType xsi:type="xsd:string">Test Company</anyType>
</Companies>
</AutofillValues>
How can I deserialize the XML file and get back the serialized data?
I just changed this part and it worked for me.
You can not deserialize the XML back, because the class ComboBox.ObjectCollection does not have a standard (parameterless) constructor. This is a limitation of the XmlSerializer class, as stated in this SO post.
There is however another problem with your current code - even if the deserialization somehow works, than you still need to assign the collection to a ComboBox control, which the deserializer still can't do.
Instead of using the ComboBox.ObjectCollection class to store the items, I would suggest using either an array or a list of objects (as #kenlacoste suggested). Such collections can be easily inserted into the ComboBox using
the comboBox.Items.AddRange(arrayOfObjects) method.
Another refactoring would be to extract the serialization logic of the data class. Currently it is confusing to save and load the data, because I presume you want to save/fill the caller object:
save: object.save(object); - you can use the this keyword in the save method
load: object = object.load(); - same here, there is no need to return the value, use the this keyword to fill the existing properties
The changed code:
public class AutofillValues
{
private string fileName = #"d:\settings.db";
public object[] Vendors { get; set; }
public object[] Products { get; set; }
public object[] Companies { get; set; }
public void save()
{
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
// with using there is no need to close the writer explicitely
// second parameter - file is created if it does not exist
using (var writer = new StreamWriter(fileName, false))
{
x.Serialize(writer, this);
}
}
public void load()
{
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
AutofillValues av = (AutofillValues)x.Deserialize(new StreamReader(fileName));
this.Companies = av.Companies;
this.Vendors = av.Vendors;
this.Products = av.Products;
}
}
IMO the modified code is easier to read and understand:
var afv = new AutofillValues();
afv.load();
//use avf.Products
// or afv.save();
I would also suggest to extract the data that needs to be saved in an extra class, for example:
[Serializable]
public class AutofillValuesData
{
public Object[] Vendors { get; set; }
public Object[] Products { get; set; }
public Object[] Companies { get; set; }
}
In the class AutofillValues remove the three properties and leave just one:
public AutofillValuesData Data { get; set; }
Then the logic can be modified to fill the ComboBox controls from the filled data object. This way your data will not be hardwired to the UI and this would make the code more maintainable. You can use a helper like AutoMapper to remove the repetitive code (like mappig objA.Vendors to objB.Vendors).

Categories

Resources