This follows on from my previous question Serialize list of interfaces using XML Serialization
public class MeterWalkOrder
{
public MeterWalkOrder()
{
Meters = new List<IMeter>();
}
public String Name { get; set; }
[XmlIgnore]
public List<IMeter> Meters { get; set; }
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public List<Meter> SerializableMeters
{
get
{
return Meters.Cast<Meter>().ToList();
}
set
{
Meters = new List<IMeter>(value);
}
}
}
public interface IMeter {
int MeterID { get; set; }
}
public class Meter : IMeter {
public int MeterID { get; set; }
public string SerialNumber { get; set; }
}
}
I am using the extension method below to deserialize the XML back into my object (ideally I would prefer the extension method to be off of object, but I not too comfortable with extension methods so I have left like this for now)...
public static class SerializationExtensions
{
public static T LoadFromXML<T>(this string xmlString)
{
T returnValue = default(T);
XmlSerializer serial = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(xmlString);
object result = serial.Deserialize(reader);
if (result != null && result is T)
{
returnValue = ((T)result);
}
reader.Close();
return returnValue;
}
....However, when I give the XML below....
<?xml version="1.0"?>
<MeterWalkOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Red Route</Name>
<Meters>
<Meter>
<MeterID>1</MeterID>
<SerialNumber>12345</SerialNumber>
</Meter>
<Meter>
<MeterID>2</MeterID>
<SerialNumber>SE</SerialNumber>
</Meter>
</Meters>
</MeterWalkOrder>
No meters are populated?
Does anyone know what could cause this problem? The XML is valid and SerializeableMeters is simply a property that reads from and writes to Meters but casting it as a concrete class due to the known issues with using interfaces in serialization
The problem is that XmlSerializer deserializes a property referring to a class implementing IList<T> in the following way:
It calls the getter to get the list. If null, it allocates a list and sets it via the setter. It holds onto the list in some local variable while reading it.
It deserializes each list element, and adds it to the list it is holding.
And that's it. It never calls the containing class's list property setter afterwards.
You can verify this by replacing your List<Meter> with an ObservableCollection<Meter>, and setting a debug listener for when the collection changes:
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public ObservableCollection<Meter> SerializableMeters
{
get
{
Debug.WriteLine("Returning proxy SerializableMeters");
var list = new ObservableCollection<Meter>(Meters.Cast<Meter>());
list.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(list_CollectionChanged);
return list;
}
set
{
Debug.WriteLine("Setting proxy SerializableMeters");
Meters = new List<IMeter>(value.Cast<IMeter>());
}
}
static void list_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var collection = (IList<Meter>)sender;
Debug.WriteLine("Proxy collection changed to include : ");
foreach (var item in collection)
Debug.WriteLine(" " + item.ToString());
}
Doing so, you'll see the following debug output:
Returning proxy SerializableMeters
Returning proxy SerializableMeters
Returning proxy SerializableMeters
Returning proxy SerializableMeters
Proxy collection changed to include :
Meter: 1, 12345
Proxy collection changed to include :
Meter: 1, 12345
Meter: 2, SE
As you can see, the list is never set back.
Luckily, there's an easy alternative. If you return a proxy array instead of a proxy List, XmlSerializer will allocate the array itself, populate it, and set it via the setter -- which is just what you want!
[XmlArrayItem(ElementName = "Meter")]
[XmlArray(ElementName = "Meters")]
public Meter [] SerializableMeters
{
get
{
return Meters.Cast<Meter>().ToArray();
}
set
{
Meters = new List<IMeter>(value.Cast<IMeter>());
}
}
And then later
var meters = xml.LoadFromXML<MeterWalkOrder>();
Debug.Assert(meters.Meters.Count == 2); // No assert.
Related
I'm working with some third party XML that has no formally defined schema, only example XML. I have several thousand XML files from this third party. There is no guarantee that every possible element lies within one or more of these files. The third party service could send me a new file with a new element!
I can view these files and reverse engineer types relatively easily.
For example:
<MyObject>
<MyProperty>Some value</MyProperty>
</MyObject>
Could deserialize to
public class MyObject
{
public string MyProperty { get; set; }
}
No problems so far.
But what if I attempt to deserialize this:
<MyObject>
<MyProperty>Some value</MyProperty>
<MyOtherProperty>Some value</MyOtherProperty>
</MyObject>
into my class above? I want it to throw an exception, so I can be notified that my class does not accommodate MyOtherProperty.
Is there a way to do this?
I'd like to share the code I wrote using the accepted answer. Using the below utility method, I can deserialize without checks for unknown stuff, and with checks, by setting strict=true.
I hope readers find this useful!
public static T XmlDeserialize<T>(string xml, bool strict = false)
{
using (var stringReader = new StringReader(xml))
{
using (var xmlTextReader = new XmlTextReader(stringReader))
{
var xmlSerializer = new XmlSerializer(typeof(T));
if (strict)
{
var options = new XmlDeserializationEvents();
options.OnUnknownElement += (sender, args) =>
{
throw new XmlDeserializationException(xml, $"Unexpected Element {args.Element.LocalName} on line {args.LineNumber}.");
};
options.OnUnknownAttribute += (sender, args) =>
{
throw new XmlDeserializationException(xml, $"Unexpected Element: {args.Attr.LocalName} on line {args.LineNumber}.");
};
options.OnUnknownNode += (sender, args) =>
{
throw new XmlDeserializationException(xml, $"Unexpected Element: {args.LocalName} on line {args.LineNumber}.");
};
return (T)xmlSerializer.Deserialize(xmlTextReader, options);
}
return (T)xmlSerializer.Deserialize(xmlTextReader);
}
}
}
And that exception class I'm throwing looks like this:
public class XmlDeserializationException : Exception
{
public string Xml { get; private set; }
public XmlDeserializationException(
string xml, string message) : base (message)
{
Xml = xml;
}
}
I can check my logs and look up the line number in the actual xml. Works perfectly. Thanks, pfx.
The XmlSerializer has a Deserialize overload allowing to pass in an options element by which to hook to some events; eg. OnUnknownElement.
XmlDeserializationEvents options = new XmlDeserializationEvents();
options.OnUnknownElement += (sender, args) => {
XmlElement unknownElement = args.Element;
// throw an Exception with this info.
} ;
var o = serializer.Deserialize(xml, options) as MyObject;
The OnUnknowElement takes nested elements into account.
With the classes below
public class MyObject
{
public string MyProperty { get; set; }
public MyOtherObject Other { get; set; }
}
public class MyOtherObject
{
public string SomeProperty { get; set; }
}
and the following xml
<MyObject>
<MyProperty>Some value</MyProperty>
<Other>
<SomeProperty>...</SomeProperty>
<UFO>...</UFO>
</Other>
</MyObject>
The OnUnknowElement handler will trigger for the UFO element.
One way of doing so would be to use your current object model and create an XSD out of it. You can then check new files against that XSD and throw if it doesn't validate.
Extend your class with an XmlAnyElement container.
Any unknown elements will end up in that array.
After deserialization check whether that array is empty.
public class MyObject
{
[XmlAnyElement]
public XmlElement[] UnknownElements;
public string MyProperty { get; set; }
}
I would like to know how to ignore a specific item/index of a List<T> from being serialized using XmlSerializer.
For example, consider the following list:
...
List<int> collection = new List<int>() {0, 1, 2};
...
What I would like to achieve is when serializing the above List<int> using XmlSerializer, I want the 0 to be ignored from being serialized, so the desired result is:
...
<collection>
<int>1</int>
<int>2</int>
</collection> // as you can see - there is no <int>0</int> value.
...
Thanks.
UPDATE
The following code is a concrete example of my question:
[Serializable]
public class Ball
{
private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(Ball));
public Ball()
{
// value 1 is a default value which I don't want to be serialized.
Points = new List<int>() { 1 };
IsEnabled = false;
}
public List<int> Points { get; set; }
public bool IsEnabled { get; set; }
public void Save()
{
using (StreamWriter writer = new StreamWriter(FILENAME))
{
Serializer.Serialize(writer, this);
}
}
public Ball Load()
{
using (StreamReader reader = new StreamReader(FILENAME))
{
return (Ball)Serializer.Deserialize(reader);
}
}
}
I suspect you are actually trying to solve an XY problem where the real problem is the one described in the question Deserializing List with XmlSerializer Causing Extra Items: when you serialize and deserialize a collection property that has default items added in the constructor, the default items get duplicated, because the deserialized default items get added to the latest default items.
That answer to that question provides one workaround, namely to move initialization of the default collection entries out of the constructor. If that's not convenient, you can instead introduce a proxy array property and serialize that instead of the underlying collection:
[Serializable]
public class Ball
{
public Ball()
{
Points = new List<int>() { 1 };
IsEnabled = false;
}
public bool IsEnabled { get; set; }
[XmlIgnore]
public List<int> Points { get; set; }
[XmlArray("Points")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public int[] SerializablePoints
{
get
{
return (Points == null ? null : Points.ToArray());
}
set
{
Points = ListExtensions.Initialize(Points, value);
}
}
}
public static class ListExtensions
{
public static List<T> Initialize<T>(List<T> list, T[] value)
{
if (value == null)
{
if (list != null)
list.Clear();
}
else
{
(list = list ?? new List<T>(value.Length)).Clear();
list.AddRange(value);
}
return list;
}
}
For an explanation of why the property must be an array, see XML Deserialization of collection property with code defaults.
Better solution is to create a new collection with LINQ query and pass to serializer, like
List<int> collection = new List<int>(){ 0, 1, 2, 3 };
using (var fs = new StreamWriter("serialized.txt"))
{
XmlSerializer serializer = new XmlSerializer(collection.GetType());
serializer.Serialize(fs, collection.Where(x => x != 0).ToList());
}
There seems to be a bug / inconsistency in the Microsoft XmlSerializer: If you have a property marked with a System.ComponentModel.DefaultValue attribute, this does not get serialized. Fair enough - this could be seen as an expected behavior.
The problem is the same attribute is not respected when deserializing. The code below illustrates the issue.
Question is how could I bypass this? I have potentially hundreds of business classes with default values used in the UI tier (Views), so default value initialization in constructor is not an option. It has to be something generic. I could create a completely new default attribute, but it seems like duplicate work. Do you see a way to override the XmlSerializer behavior or should I use just another serializer that does the job better?
The example code:
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
[TestMethod]
public void SimpleDefaultValueTest()
{
// Create object and set the property value TO THE DEFAULT
var before = new DefaultValueTestClass();
before.Foo = 10000;
// Serialize => xml
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(DefaultValueTestClass));
string xml;
using (var stream = new System.IO.StringWriter())
{
serializer.Serialize(stream, before);
xml = stream.ToString();
}
// Deserialize the same object
DefaultValueTestClass after;
using (var reader = new System.IO.StringReader(xml))
{
after = (DefaultValueTestClass)serializer.Deserialize(reader);
}
// before.Foo = 10000
// after.Foo = 0
Assert.AreEqual(before.Foo, after.Foo);
}
It is your job to implement the defaults; [DefaultValue] merely says "this is the default, you don't need to worry about this" - it doesn't apply it. This applies not just to XmlSerializer, but to the core System.ComponentModel API to which [DefaultValue] belongs (which drives things like the bold / not-bold in PropertyGrid, etc)
Basically:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
[DefaultValue(10000)]
public int Foo { get; set; }
}
will work in the way you expect. If you want it to serialize whether or not it is that particular value, then the correct implementations is:
public class DefaultValueTestClass
{
public DefaultValueTestClass()
{
Foo = 10000;
}
public int Foo { get; set; }
}
If you want to preserve the [DefaultValue], but want it to always serialize, then:
public class DefaultValueTestClass
{
[DefaultValue(10000)]
public int Foo { get; set; }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public bool ShouldSerializeFoo() { return true; }
}
Where ShouldSerialize* is another pattern from System.ComponentModel that is recognised by several serializers.
And here's some UI code to show that XmlSerializer is actually doing exactly the same things that the UI code (built on System.ComponentModel) has always done:
using System.ComponentModel;
using System.Windows.Forms;
static class Program
{
[System.STAThread]
static void Main()
{
Application.EnableVisualStyles();
using (var form = new Form())
using (var grid = new PropertyGrid())
{
grid.Dock = DockStyle.Fill;
var obj = new DefaultValueTestClass
{ // TODO - try with other numbers to
// see bold / not bold
Foo = 10000
};
// note in the grid the value is shown not-bold; that is
// because System.ComponentModel is saying
// "this property doesn't need to be serialized"
// - or to show it more explicitly:
var prop = TypeDescriptor.GetProperties(obj)["Foo"];
bool shouldSerialize = prop.ShouldSerializeValue(obj);
// ^^^ false, because of the DefaultValueAttribute
form.Text = shouldSerialize.ToString(); // win title
grid.SelectedObject = obj;
form.Controls.Add(grid);
Application.Run(form);
}
}
}
public class DefaultValueTestClass
{
[System.ComponentModel.DefaultValue(10000)]
public int Foo { get; set; }
}
I want to deserialize a JSON message received from TCP/IP in my C# application. I have really simple classes with one or two Properties at most, but they are derived. For example:
public class SemiCommand
{
public SemiCommand()
{
UID = string.Empty;
}
public SemiCommand(string uid)
{
UID = uid;
}
public string UID
{
get;
set;
}
public new string ToString()
{
return new JavaScriptSerializer().Serialize(this);
}
}
public class ResponseCommand : SemiCommand
{
protected const string DEFAULT_INFO = "OK";
public ResponseCommand()
{
UID = string.Empty;
Info = DEFAULT_INFO;
}
public ResponseCommand(string uid)
{
UID = uid;
Info = DEFAULT_INFO;
}
public string Response
{
get;
set;
}
public string Info
{
get;
set;
}
public class GetInfoResponseCommand : ResponseCommand
{
public GetInfoResponseCommand()
: base()
{
Response = "GetInfoResponse";
}
public List<ClientProcess> Processes
{
get;
set;
}
}
I made a few attempts with DataMember and DataContract attributes to solve my problem, but it didn't fix it.
The problem lies here:
private Type[] _deserializationTypes = new Type[] { typeof(StartProcessesRequestCommand), typeof(RequestCommand) };
public RequestCommand TranslateTCPMessageToCommand(string messageInString)
{
RequestCommand requestCommand = null;
for (int i = 0; i < _deserializationTypes.Length; i++)
{
try
{
requestCommand = TryDeserialize(_deserializationTypes[i], messageInString);
break;
}
catch
{
}
}
if (requestCommand == null || (requestCommand != null && requestCommand.Command == string.Empty))
{
throw new System.Runtime.Serialization.SerializationException("Unable to deserialize received message");
}
else
{
return requestCommand;
}
}
If i want to deserialize with a proper format, containing all the necessary properties, it works:
This works: {"Response":"SomeResponse","Info":"Information","UID":"123"}
However, this also works: {"nonexistingProperty":"value"}
The second one also creates a RequestCommand with property values set to null.
1st question: How can I force my messagetranslator function to make RequestCommand only if it receives all of the required properties
2nd question: If i have multiple command types which are derived from one or more ancestors, how is it possible, to deserialize it automatically from the "deepest" class if the received properties allow it.
EDIT:
I serialize/deserialize with these two
private RequestCommand TryDeserialize(Type type, string messageInString)
{
JavaScriptSerializer js = new JavaScriptSerializer();
return (RequestCommand)js.Deserialize(messageInString, type);
}
public string TranslateCommandToTCPMessage(ResponseCommand command)
{
JavaScriptSerializer js = new JavaScriptSerializer();
return js.Serialize(command);
}
The break in the try catch supposed to end the loop if the TrySerialize doesn't throw an exception, therefore the deserialization was successful.
If you actually used DataContractJsonSerializer, and you decorated the members / types in question with DataMember / DataContract, you can actually get this to happen. To do this, you can decorate ALL data members with [IsRequired=true]. This would throw an exception if the members weren't present on the wire.
Another option you have is to use IObjectReference, which let you translate one object to another post-serialization, and you can do this depending on what is deserialized from the wire.
I have this c# class:
public class Test
{
public Test() { }
public IList<int> list = new List<int>();
}
Then I have this code:
Test t = new Test();
t.list.Add(1);
t.list.Add(2);
IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
StringWriter sw = new StringWriter();
XmlSerializer xml = new XmlSerializer(t.GetType());
xml.Serialize(sw, t);
When I look at the output from sw, its this:
<?xml version="1.0" encoding="utf-16"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
the values 1,2 I added to the list member variable dont show up.
So how can I fix this ? I made the list a property but it still doesnt seem to work.
I am using xml serialization here, are there any other serializers ?
I want performance! Is this the best approach ?
--------------- UPDATE BELOW -------------------------
So the actual class I want to serialize is this:
public class RoutingResult
{
public float lengthInMeters { get; set; }
public float durationInSeconds { get; set; }
public string Name { get; set; }
public double travelTime
{
get
{
TimeSpan timeSpan = TimeSpan.FromSeconds(durationInSeconds);
return timeSpan.TotalMinutes;
}
}
public float totalWalkingDistance
{
get
{
float totalWalkingLengthInMeters = 0;
foreach (RoutingLeg leg in Legs)
{
if (leg.type == RoutingLeg.TransportType.Walk)
{
totalWalkingLengthInMeters += leg.lengthInMeters;
}
}
return (float)(totalWalkingLengthInMeters / 1000);
}
}
public IList<RoutingLeg> Legs { get; set; } // this is a property! isnit it?
public IList<int> test{get;set;} // test ...
public RoutingResult()
{
Legs = new List<RoutingLeg>();
test = new List<int>(); //test
test.Add(1);
test.Add(2);
Name = new Random().Next().ToString(); // for test
}
}
But the XML produced by the serializer is this:
<RoutingResult>
<lengthInMeters>9800.118</lengthInMeters>
<durationInSeconds>1440</durationInSeconds>
<Name>630104750</Name>
</RoutingResult>
???
its ignoring both of those lists ?
1) Your list is a field, not a property, and the XmlSerializer will only work with properties, try this:
public class Test
{
public Test() { IntList = new List<int>() }
public IList<int> IntList { get; set; }
}
2) There are other Serialiation options, Binary the main other one, though there is one for JSON as well.
3) Binary is probably the most performant way, since it is typically a straight memory dump, and the output file will be the smallest.
list is not a Property. Change it to a publicly visible property, and it should be picked up.
I figured it out that XmlSerializer doesnt work if I use IList so I changed it to List, that made it work. As Nate also mentioned.