XML deserialization of different classes in file - c#

I get a file with different content (currently 4 different classes), either
<ClassA><!-- content --></ClassA>
or
<ClassB><!-- content --></ClassB>
or ...
At time of parsing I have no further information which class is in the file.
So, currently, I try to parse by trial and error:
try
{
ClassA result = (ClassA)new XmlSerializer(typeof(ClassA)).Deserialize(reader);
if(!(result is null)) { \\do something }
}
catch (Exception) {}
And the same for ClassB and so on ...
Is there a more elegant way to parse the classes?
I can give all classes the same base class, although the are quite different in their form.

I solved this issue with XmlSerializer.CanDeserialize(XmlReader)
using (var reader = XmlReader.Create(stream))
{
foreach (var type in types)
{
var serializer = new XmlSerializer(type);
if (serializer.CanDeserialize(reader))
{
return serializer.Deserialize(reader);
}
}
throw new XmlException("Invalid xml type");
}

Related

XML deserialize brings back empty list

I have a dictionary of abilityobjects <id, abilityObj> that I'm trying to serialize in XML. Because you can't XML Serialize a dictionary, I change it into a list on serialization
public class BattleSerializable : TyrantSerializable
{
[XmlIgnoreAttribute]
[NonSerialized]
[DoNotSerialize]
public Dictionary<int, AbilityObjectSerializable> battleObjects;
public List<AbilityObjectSerializable> serializedBattleObjects
{
get
{
if (battleObjects == null)
{
battleObjects = new Dictionary<int, AbilityObjectSerializable>();
}
return battleObjects.Select(x => x.Value).ToList();
}
set
{
battleObjects = value.ToDictionary(x => x.entityId, x => x);
}
}
It serializes correctly. I.e. the XML that gets saved makes sense
<BattleSerializable>
...
<serializedBattleObjects>
<AbilityObjectSerializable xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" d3p1:type="FireballObject">
<hexDirection>southeast</hexDirection>
<gridX>0</gridX>
<gridZ>7</gridZ>
<entityId>3</entityId>
<lastAnimation>STATUE</lastAnimation>
<timer>0</timer>
<abilityPos>2</abilityPos>
<abilityType>FIREBALL</abilityType>
<health>100</health>
<tilesPerTurn>2</tilesPerTurn>
<jump>1</jump>
<fall>99</fall>
<damage>5</damage>
<lineTraversed>
<xDisplace>1</xDisplace>
<zDisplace>-2</zDisplace>
<endTileFacing>east</endTileFacing>
</lineTraversed>
<moveDirection>
<xDisplace>1</xDisplace>
<zDisplace>-2</zDisplace>
<endTileFacing>east</endTileFacing>
</moveDirection>
</AbilityObjectSerializable>
</serializedBattleObjects>
</BattleSerializable>
But when I try to then -load- this XML and turn it into actual C# objects, this list comes in empty for some reason, causing the app to blow up.
What am I missing? All the other lists in this class serialize/deserialize correctly.
My load code:
public BattleSerializable Load(string path)
{
var serializer = new XmlSerializer(typeof(BattleSerializable));
try
{
using (var stream = new FileStream(path, FileMode.Open))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(stream);
string xmlString = xmlDoc.InnerXml;
BattleSerializable bs = (BattleSerializable)this.LoadFromXML(xmlString);
return bs;
}
}
catch (Exception e)
{
throw new SettingLoadException("Settings failed validation");
}
}
The way a lot of serializers work is by calling Add on a list, only actually assigning anything back to the setter if the serializer created the list (perhaps because it was null, or fixed size such as an array). So imagine the serializer doing:
var list = obj.SomeProperty;
while (moreOfTheSame)
list.Add(ReadOneOfThose());
It never calls the setter, so any logic in there: irrelevant. You'll probably need a custom list type here, or perhaps more simply: have a nice simple POCO/DTO model that just maps to the serialization shape with no fun logic, and project between this model and your domain model separately to serialization.

Adding object to JSON file

I'm creating a software on which I added a profiles feature where the user can create profile to load his informations faster. To store these informations, I'm using a JSON file, which contains as much objects as there are profiles.
Here is the format of the JSON file when a profile is contained (not the actual one, an example) :
{
"Profile-name": {
"form_email": "example#example.com",
//Many other informations...
}
}
Here is the code I'm using to write the JSON and its content :
string json = File.ReadAllText("profiles.json");
dynamic profiles = JsonConvert.DeserializeObject(json);
if (profiles == null)
{
File.WriteAllText(jsonFilePath, "{}");
json = File.ReadAllText(jsonFilePath);
profiles = JsonConvert.DeserializeObject<Dictionary<string, Profile_Name>>(json);
}
profiles.Add(profile_name.Text, new Profile_Name { form_email = form_email.Text });
var newJson = JsonConvert.SerializeObject(profiles, Formatting.Indented);
File.WriteAllText(jsonFilePath, newJson);
profile_tr.Nodes.Add(profile_name.Text, profile_name.Text);
debug_tb.Text += newJson;
But when the profiles.json file is completely empty, the profile is successfully written, but when I'm trying to ADD a profile when another one already exists, I get this error :
The best overloaded method match for 'Newtonsoft.Json.Linq.JObject.Add(string, Newtonsoft.Json.Linq.JToken)' has some invalid arguments on the profiles.Add(); line.
By the way, you can notice that I need to add {} by a non-trivial way in the file if it's empty, maybe it has something to do with the error ?
The expected output would be this JSON file :
{
"Profile-name": {
"form_email": "example#example.com",
//Many other informations...
},
"Second-profile": {
"form_email": "anotherexample#example.com"
//Some other informations...
}
}
Okay so I found by reading my code again, so I just replaced dynamic profiles = JsonConvert.DeserializeObject(json); to dynamic profiles = JsonConvert.DeserializeObject<Dictionary<string, Profile_Name>>(json);.
But it still don't fix the non-trivial way I use to add the {} to my file...
The object the first DeserializeObject method returns is actually a JObject, but below you deserialize it as a Dictionary. You shouldn't be mixing the types, choose either one.
If you use the JObject then to add objects you need to convert them to JObjects:
profiles.Add(profile_name.Text, JObject.FromObject(new Profile_Name { form_email = form_email.Text }));
In both cases, when the profile is null you just need to initialize it:
if (profiles == null)
{
profiles = new JObject(); // or new Dictionary<string, Profile_Name>();
}

Deserializing abstract Queue<T> in Json [duplicate]

This question already has an answer here:
Deserialize a List<AbstractClass> with newtonsoft.json
(1 answer)
Closed 7 years ago.
I have a Queue of an abstract class KVP. I queue 2 different objects which inherit from KVP. Everything works fine when I serialize the queue, but since KVP cannot be constructed it fails on deserialization.
If it was a single non generic object I could deserialize as dynamic, but I'm not sure how to deserialize a queue that could hold both events and IDs.
Sample code:
public virtual async Task<bool> LoadFromFile(string FileName, bool addToExistingQueue,bool DeleteFileAfterLoad = true)
{
try
{
IFile File = await PCLStorage.FileSystem.Current.LocalStorage.GetFileAsync(FileName);
var serializedText = await File.ReadAllTextAsync();
var mQueue = JsonConvert.DeserializeObject<Queue<T>>(serializedText,jss);
if (!addToExistingQueue)
{
_queue = new ConcurrentQueue<T>();
}
while (mQueue.Count > 0)
{
_queue.Enqueue(mQueue.Dequeue());
}
if (DeleteFileAfterLoad)
{
await File.DeleteAsync();
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine("Could not load File. Exception Message: " + ex.Message);
return false;
}
}
public virtual async Task<bool> WriteToFile(string FileName)
{
try
{
Debug.WriteLine("Writing File: " + FileName);
var File = await FileSystem.Current.LocalStorage.CreateFileAsync(FileName, CreationCollisionOption.ReplaceExisting);
var serializedText = JsonConvert.SerializeObject(_queue.ToList(),jss);
await File.WriteAllTextAsync(serializedText);
return true;
}
catch (Exception ex)
{
Debug.WriteLine("Could not write File with exception message: " + ex.Message);
return false;
}
}
You could
Enable TypeNameHandling (in both serialization and deserialization):
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var serializedText= JsonConvert.SerializeObject(mQueue, settings);
And then later
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
var mQueue = JsonConvert.DeserializeObject<Queue<T>>(serializedText, settings);
This adds an extra "$type" property to your polymorphic classes, as is described here.
Before choosing this solution, for a discussion of possible security concerns using TypeNameHandling, see TypeNameHandling caution in Newtonsoft Json and How to configure Json.NET to create a vulnerable web API.
Write a custom converter that looks at the actual properties and chooses which derived class to use, as is discussed here: Deserializing polymorphic json classes without type information using json.net. This avoids the need for the extra "$type" property.
This should work if you use the following setting in the JSON.NET serialiser settings:
var settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto};
This will encode the object type into the JSON stream, which will help the deserializer determined the specific object type to construct during de-serialization.

List of Objects Serialization xml fault

I am making a WPF application in which I save list of object on exit of my WPF Application. And get the list of objects on system startup. Everything works fine initially. But Some times it gives the serialization Exception. After getting the exception I looked of the xml serialized file. But it seem to me that the exception was thrown because the xml file was not formed properly. When I corrected it. It again worked fine.
public static class IsolatedStorageCacheManager<T>
{
public static void store(T loc)
{
IsolatedStorageFile appStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain, null, null);
using(IsolatedStorageFileStream fileStream=appStore.OpenFile("myFile21.xml",FileMode.OpenOrCreate))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(fileStream, loc);
}
}
public static T retrieve()
{
T obj = default(T);
IsolatedStorageFile appStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain, null, null);
if (appStore.FileExists("myFile21.xml"))
{
using (IsolatedStorageFileStream fileStream = appStore.OpenFile("myFile21.xml", FileMode.Open))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
try
{
obj = (T)serializer.ReadObject(fileStream);
}
catch (SerializationException e)
{
Console.WriteLine(e.StackTrace);
}
}
}
return obj;
}
}
The first thing to do is make sure that the objects passed to store are of a type supported by the DataContractSerializer.
The easiest thing to do is check all store calls yourself.
You could also create a validation method or even better, see if anyone else has implemented one. This method could validate the loc object and return a boolean and be called at the beginning of the store method inside a System.Diagnostics.Debug.Assert call so that it will only run on a debug configuration. Note though that this method could be quite tricky because you will have to validate type T for all the cases mentioned in the specification of DataContractSerializer and if T is a generic validate T's parameter(s) as well.

IQueryable<> Serialization - C#-VS2008-WCF

I built my *.dbml file with the required tables and that generated the relationships, 1 to many.
One of my methods in my WCF Service library has this query
IQueryable<Client>localClient = from c in db.Clients
where c.ClientID.Equals(2612)
select c;
foreach(Client currentClient in localClient)
{
//Call serialize method here
}
One of the table which client has a 1 to many relationships is Client - Employee
With 'localClient', I want to serialize that and return to the invoker. However it tells me that the the XML document could not be formed.
This is the InnerException:
A circular reference was detected while serializing an object of type TestDB_Public.Employee.
My serialization code
public string Serialize(object o, XmlSerializerNamespaces ns)
{
try
{
System.IO.MemoryStream m = new System.IO.MemoryStream();
if (ns != null)
serializer.Serialize(m, o, ns);
else
serializer.Serialize(m, o);
m.Position = 0;
byte[] b = new byte[m.Length];
m.Read(b, 0, b.Length);
return System.Text.UTF8Encoding.UTF8.GetString(b);
}
catch (Exception ex)
{
return "Ex = " + ex.ToString();
}
}
Is serialization of IQueryable<> with 1 to many relationships not possible?
What exactly is the error message? Note that you have to serialize something concrete like a list or array of objects (not a query).
If you want queryable over the wire, look at ADO.NET Data Services, which does this.
Also - have you set the serialization mode to "unidirectional" in the dbml designer? As long as there are no loops, it should work fine.
You can't serialise an object graph that has cyclical relationships:
class Employee
{
Employee Manager;
List<Employee> Employees;
}
var bossMan = new Employee();
var emp2 = new Employee{Manager = bossMan}
var bossMan.Employees.Add(emp2);
If you now try to serialise bossman or emp2, you will get the exception.
Have a look at this post, check the Cyclic Object Graphs for a solution.
Marc:
For some reason it is not allowing me to add a comment;
I added this
[Table(Name="dbo.Client")]
[DataContract(IsReference=true)]
public partial class Client: INotifyPropertyChanging, INotifyPropertyChanged
{
..//
private EntitySet<ClEmp> _ClEmp;
[Association(N...)]
[DataMember(Order=70, EmitDefaultValue=false)]
public EntitySet<ClEmp> ClEmps
}
My serialization is this:
DataContractSerializer ser =
new DataContractSerializer(typeof(Client));
var ms = new System.IO.MemoryStream();
ser.WriteObject(ms, r);
ms.Seek(0, System.IO.SeekOrigin.Begin);
var sr = new System.IO.StreamReader(ms);
var xml = sr.ReadToEnd();
when i look at var xml, i do not get my enity set ClEmp.

Categories

Resources