How to serialize class as attribute? - c#

I am trying to serialize a class as an attribute. Here's the use case: I need to store a large number of engineering parameters in an xml config file. Scientific notation is involved, and it is required by the customer that the entered values remain in the exact form the user entered them. For example, if someone enters "5.3e-1" then it must remain like that, and cannot be converted into, say, "0.53". To this end I've created a Params class that stores both the entered string and double values (actually storing the double values is only for processing efficiency later). Now here's the trick: I only want the string value to be serialized, and I want that to serialize as an attribute.
For example, if an object contains two parameters, A and B, where the string values are A.stringVal = "1.2e5" and B.stringVal = "2.0" then I would like:
public class MyObject
{
[XmlAttribute]
public MyParam A { get; set; }
[XmlAttribute]
public MyParam B { get; set; }
...more stuff...
}
to serialize to:
<myObject A="1.2e5" B="2.0">
more stuff...
</myObject>
My question is very similar to one asked here. Unlike him I am fine with implementing IXmlSerializable (and would prefer to), I just can't make it work. When I try to serialize it I get a cryptic exception saying, "There was error reflection type." What am I doing wrong?
public class MyParam : IXmlSerializable
{
string name;
string stringVal;
double doubleVal;
public string Val
{
get
{
return stringVal;
}
set
{
stringVal = value;
doubleVal = double.Parse(value);
}
}
public double DoubleVal
{
get
{
return doubleVal;
}
set
{
doubleVal = value;
stringVal = value.ToString();
}
}
public MyParam(string name)
{
this.name = name;
this.stringVal = string.Empty;
this.doubleVal = double.NaN;
}
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteAttributeString(name, stringVal);
}
}

To get what you want you need to control the serialization on the container that holds the properties, not the property itself. You can still encapsulate the serialization code in the property however, see below.
public class MyObject : IXmlSerializable
{
public MyParam A { get; set; }
public MyParam B { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
//Call each properties "WriteAttribute" call.
A.WriteAttribute(writer);
B.WriteAttribute(writer);
}
}
public class MyParam
{
string name;
string stringVal;
double doubleVal;
public string Val
{
get
{
return stringVal;
}
set
{
stringVal = value;
doubleVal = double.Parse(value);
}
}
public double DoubleVal
{
get
{
return doubleVal;
}
set
{
doubleVal = value;
stringVal = value.ToString();
}
}
public MyParam(string name)
{
this.name = name;
this.stringVal = string.Empty;
this.doubleVal = double.NaN;
}
internal void WriteAttribute(XmlWriter writer)
{
writer.WriteAttributeString(name, stringVal);
}
}
class Program
{
static void Main(string[] args)
{
var test = new MyObject()
{
A = new MyParam("A"),
B = new MyParam("B"),
};
test.A.Val = "1.2e5";
test.B.Val = "2.0";
var ser = new XmlSerializer(typeof(MyObject));
var sb = new StringBuilder();
using (var stream = new StringWriter(sb))
{
ser.Serialize(stream, test);
}
Console.WriteLine(sb);
Console.ReadLine();
}
}
Outputs
<?xml version="1.0" encoding="utf-16"?>
<MyObject A="1.2e5" B="2.0" />
If you don't need the name of the property in the property itself you can modify the code to the following.
public class MyObject : IXmlSerializable
{
//....
public void WriteXml(XmlWriter writer)
{
//Call each properties "WriteAttribute" call.
A.WriteAttribute(writer, "A");
B.WriteAttribute(writer, "B");
}
}
public class MyParam
{
//...
public MyParam()
{
this.stringVal = string.Empty;
this.doubleVal = double.NaN;
}
internal void WriteAttribute(XmlWriter writer, string name)
{
writer.WriteAttributeString(name, stringVal);
}
}

After I've added the parameterless constructor, I got the following exception:
The element 'A' type ConsoleApplication1.MyParam can not be serialized. XmlAttribute / XmlText can not be used to encode types that implement IXmlSerializable.
After removing IXmlSerializable I got an exception XmlAttribute/XmlText cannot be used to encode complex types. And found this answer
and this.
So, I think, it is better to find another way to serialize attributes.

Related

Protobuf-net Serializing Parent Class inherited object property marked as [ProtoIgnore()] throwing "No serializer defined for type: System.Object"

All the objects in our system inherit a base class which has got a property of type object.
I have tried adding protoignore attribute to all the properties of the base class as well but that doesn't seem to work as well.
class Program
{
static void Main(string[] args)
{
Vehicle vehicle = new Vehicle();
vehicle.BodyStyleDescription = "4x4";
vehicle.BodyStyleText = "Prestige Medium";
dynamic protobufModel = TypeModel.Create();
AddTypeToModel<Vehicle>(protobufModel);
using (MemoryStream compressed = new MemoryStream())
{
using (GZipStream gzip = new GZipStream(compressed, CompressionMode.Compress, true))
{
protobufModel.Serialize(gzip, vehicle);
}
string str = Convert.ToBase64String(compressed.GetBuffer(), 0, Convert.ToInt32(compressed.Length));
}
}
public static MetaType AddTypeToModel<T>(RuntimeTypeModel typeModel)
{
var properties = typeof(T).GetProperties().Select(p => p.Name).OrderBy(name => name);
return typeModel.Add(typeof(T), true).Add(properties.ToArray());
}
}
Following is the hierarchy of the object
public interface IObjectBaseClass
{
[ProtoIgnore()]
object Parent { get; set; }
[ProtoIgnore()]
bool IsSaved { get; set; }
[ProtoIgnore()]
string XmlAtLoad { get; set; }
}
public class ObjectBaseClass : IObjectBaseClass
{
public ObjectBaseClass()
{
}
[ProtoIgnore()]
internal object _Parent;
[ProtoIgnore()]
internal bool _IsSaved;
[ProtoIgnore()]
internal string _XmlAtLoad;
[ProtoIgnore()]
public bool IsSaved
{
get { return _IsSaved; }
set { _IsSaved = value; }
}
[ProtoIgnore()]
public object Parent
{
get { return _Parent; }
set { _Parent = value; }
}
[ProtoIgnore()]
public string XmlAtLoad
{
get { return _XmlAtLoad; }
set { _XmlAtLoad = value; }
}
}
public class Vehicle : ObjectBaseClass
{
private string _BodyStyleText;
private string _BodyStyleDescription;
public string BodyStyleDescription
{
get { return _BodyStyleDescription; }
set { _BodyStyleDescription = value; }
}
public string BodyStyleText
{
get { return _BodyStyleText; }
set { _BodyStyleText = value; }
}
}
Your problem is that when you do typeModel.Add(typeof(T), true).Add(properties.ToArray()) you are adding all properties of T to the runtime type model, including those marked with ProtoIgnore. You can see this by calling the debugging method protobufModel.GetSchema(typeof(Vehicle)) which returns:
message Object {
}
message Vehicle {
optional string BodyStyleDescription = 1;
optional string BodyStyleText = 2;
optional bool IsSaved = 3;
optional Object Parent = 4;
optional string XmlAtLoad = 5;
}
To avoid adding properties marked with [ProtoIgnore], you could do:
public static MetaType AddTypeToModel<T>(RuntimeTypeModel typeModel)
{
var properties = typeof(T)
.GetProperties()
.Where(p => !p.GetCustomAttributes<ProtoIgnoreAttribute>().Any())
.Select(p => p.Name)
.OrderBy(name => name);
return typeModel.Add(typeof(T), true).Add(properties.ToArray());
}
Alternatively, since you are manually annotating some of your models with protobuf attributes anyway, you could mark the derived types with [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)], e.g.:
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Vehicle : ObjectBaseClass
{
private string _BodyStyleText;
private string _BodyStyleDescription;
public string BodyStyleDescription
{
get { return _BodyStyleDescription; }
set { _BodyStyleDescription = value; }
}
public string BodyStyleText
{
get { return _BodyStyleText; }
set { _BodyStyleText = value; }
}
}
Using either method, the schema for Vehicle becomes:
message Vehicle {
optional string BodyStyleDescription = 1;
optional string BodyStyleText = 2;
}
This is what you require.

How to auto-ignore all properties with no corresponding constructor parameter when serializing

Is there some non-attribute-based method of ignoring all properties that don't have a corresponding constructor parameter when serializing? For example, when serializing this class, property Combo should be ignored. A round-trip serialization/deserialization of an instance of MyClass doesn't require Combo to be serialized. Ideally I could use some out-of-the-box setting.
public class MyClass
{
public MyClass(int myInt, string myString)
{
this.MyInt = myInt;
this.MyString = myString;
}
public int MyInt { get; }
public string MyString { get; }
public string Combo => this.MyInt + this.MyString;
}
You can do this with a custom IContractResolver:
public class ConstructorPropertiesOnlyContractResolver : DefaultContractResolver
{
readonly bool serializeAllWritableProperties;
public ConstructorPropertiesOnlyContractResolver(bool serializeAllWritableProperties)
: base()
{
this.serializeAllWritableProperties = serializeAllWritableProperties;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
if (contract.CreatorParameters.Count > 0)
{
foreach (var property in contract.Properties)
{
if (contract.CreatorParameters.GetClosestMatchProperty(property.PropertyName) == null)
{
if (!serializeAllWritableProperties || !property.Writable)
property.Readable = false;
}
}
}
return contract;
}
}
Then use it like:
var settings = new JsonSerializerSettings { ContractResolver = new ConstructorPropertiesOnlyContractResolver(false) };
var json = JsonConvert.SerializeObject(myClass, Formatting.Indented, settings );
Pass true for serializeAllWritableProperties if you also want to serialize read/write properties that are not included in the constructor parameter list, e.g. AnUnrelatedReadWriteProperty in:
public class MyClass
{
public MyClass(int myInt, string myString)
{
this.MyInt = myInt;
this.MyString = myString;
}
public int MyInt { get; private set; }
public string MyString { get; private set; }
public string Combo { get { return this.MyInt + this.MyString; } }
public string AnUnrelatedReadWriteProperty { get; set; }
}
Note you may want to cache your contract resolver for best performance.

Ignore null values - Serialization

How can I set up the System.Runtime.Serialization serializer to ignore null values?
Or do I have to use the XmlSerializer for that? If so, how?
(I don't want <ecommerceflags i:nil="true"/> tags like this to be written, if it's null then just skip it)
With System.Runtime.Serialization.DataContractSerializer you need to mark the property with [DataMember(EmitDefaultValue = false)].
Example, the code below:
class Program
{
static void Main()
{
Console.WriteLine(SerializeToString(new Person { Name = "Alex", Age = 42, NullableId = null }));
}
public static string SerializeToString<T>(T instance)
{
using (var ms = new MemoryStream())
{
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(ms, instance);
ms.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(ms))
{
return sr.ReadToEnd();
}
}
}
}
[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
[DataMember(EmitDefaultValue = false)]
public int? NullableId { get; set; }
}
prints the following:
<Person xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication4" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Age>42</Age>
<Name>Alex</Name>
</Person>
Though it has less value (except that it makes the serialized stream shorter), you can customize your serialization to achieve this.
When using System.Runtime.Serialization, you can implement the ISerializable interface:
[Serializable]
public class MyClass: ISerializable
{
private string stringField;
private object objectField;
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (stringField != null)
info.AddValue("str", stringField);
if (objectField != null)
info.AddValue("obj", objectField);
}
// the special constructor for deserializing
private MyClass(SerializationInfo info, StreamingContext context)
{
foreach (SerializationEntry entry in info)
{
switch (entry.Name)
{
case "str":
stringField = (string)entry.Value;
break;
case "obj":
objectField = entry.Value;
break;
}
}
}
}
When using XML serialization, you can implement the IXmlSerializable interface to customize the output in a similar way.
As far as I read, you could use the Specified-Feature
public int? Value { get; set; }
[System.Xml.Serialization.XmlIgnore]
public bool ValueSpecified { get { return this.Value != null; } }
To only write it if it's specified.
Another way would be
[System.Xml.Serialization.XmlIgnore]
private int? value;
public int Value { get { value.GetValueOrDefault(); } }

Deserialize XML String into Class

I am trying to deserialize an XML String into my class which is derived from another class but I am having a problem in doing so, I am getting the following error:
{"The specified type is abstract: name='DeviceRequest', namespace='', at <DeviceRequest xmlns=''>."}
I assume i am missing some decorator attribute that will inform the serializer how to deserialize this xml into the class?
Here is my code:
//Usage
DeviceRequest dreq = DeviceRequest.ParseRequest(e.XML);
//Base Class
public abstract class IFSFRequestBase
{
private string m_ApplicationSender = "";
private int m_WorkStationID = 0;
private string m_RequestID = "";
[XmlAttribute()]
public abstract string RequestType { get; set; }
public abstract byte[] Message();
[XmlAttribute()]
public string ApplicationSender
{
get
{
return m_ApplicationSender;
}
set
{
m_ApplicationSender = value;
}
}
[XmlAttribute()]
public int WorkStationID
{
get
{
return m_WorkStationID;
}
set
{
m_WorkStationID = value;
}
}
[XmlAttribute()]
public string RequestID
{
get
{
return m_RequestID;
}
set
{
m_RequestID = value;
}
}
}
//Derived Class
public abstract class DeviceRequest : IFSFRequestBase
{
private string m_TerminalID = "";
private string m_SequenceID = "";
private Output m_OutputField = null;
[XmlAttribute(), DefaultValue("")]
public string TerminalID
{
get
{
return m_TerminalID;
}
set
{
m_TerminalID = value;
}
}
[XmlAttribute(), DefaultValue("")]
public string SequenceID
{
get
{
return m_SequenceID;
}
set
{
m_SequenceID = value;
}
}
[XmlElement("Output")]
public Output OutputField
{
get
{
return m_OutputField;
}
set
{
m_OutputField = value;
}
}
public static DeviceRequest ParseRequest(string sXML)
{
XmlSerializer serializer = new XmlSerializer(typeof(DeviceRequest));
StringReader sr = new StringReader(sXML);
NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);
return (DeviceRequest)serializer.Deserialize(XMLWithoutNamespace);
}
}
// helper class to ignore namespaces when de-serializing
public class NamespaceIgnorantXmlTextReader : XmlTextReader
{
public NamespaceIgnorantXmlTextReader(System.IO.TextReader reader) : base(reader) { }
public override string NamespaceURI
{
get { return ""; }
}
}
UPDATE:
OK based on the answers here. I have updated the code, I now have a class Display that derives from DeviceRequest. I now get the following error:
{"There is an error in XML document (2, 2)."}, {"<DeviceRequest xmlns=''> was not expected."}
public class Display : DeviceRequest
{
public static Display ParseRequest(string sXML)
{
XmlSerializer serializer = new XmlSerializer(typeof(Display));
StringReader sr = new StringReader(sXML);
NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);
return (Display)serializer.Deserialize(XMLWithoutNamespace);
}
[XmlAttribute()]
public override string RequestType
{
get { return "Output"; } set { }
}
public override byte[] Message()
{
throw new NotImplementedException();
}
}
DeviceRequest dreq = Display.ParseRequest(e.XML);
As DeviceRequest is an abstract type, it cannot be instantiated directly. It is impossible to directly deserialize into instances of Device-Request. That said, if there are some non-abstract classes derived from it, please show some of them.
OK, I have resolved this issue now. Thanks for the input guys.
I needed to add:
[System.Xml.Serialization.XmlRootAttribute(ElementName = "DeviceRequest")]
to the Display Class
[System.Xml.Serialization.XmlRootAttribute(ElementName = "DeviceRequest")]
public class Display : DeviceRequest
{
public static Display ParseRequest(string sXML)
{
XmlSerializer serializer = new XmlSerializer(typeof(Display));
StringReader sr = new StringReader(sXML);
NamespaceIgnorantXmlTextReader XMLWithoutNamespace = new NamespaceIgnorantXmlTextReader(sr);
return (Display)serializer.Deserialize(XMLWithoutNamespace);
}
[XmlAttribute()]
public override string RequestType
{
get { return "O"; } set { }
}
public override byte[] Message()
{
throw new NotImplementedException();
}
}

XmlSerializer putting element name inside a property when deserializing

I have an issue with the .NET XML Serializer where I have do different XML Elements with different names that map to the same type. Basically, the objects should be exactly the same, but I want them to have a string or enum or something that identifies which of the three possible element names were used. So here's an example:
<Body>
<MyTypeA>
<Foo>bar</Foo>
</MyTypeA>
<MyTypeB>
<Foo>bar</Foo>
</MyTypeB>
</Body>
Now, for the classes, MyTypeA and MyTypeB will both be the same type. For example:
public class Body {
public MyType MyTypeA { get; set; }
public MyType MyTypeB { get; set; }
}
public class MyType {
public string Foo { get; set; }
[XmlIgnore]
public MyTypeType { get; set; }
}
public enum MyTypeType
{
MyTypeA,
MyTypeB
}
When serializing it works fine, because I can always just ensure one way or another that the enum is set properly before serializing. But when deserializing it is not getting set and I'm not sure there's a way how.
For the record, I unfortunately don't get to set the schema, otherwise I would have built it in such a way that I didn't have this problem.
If i understood your question correctly, this might help you. Just write you XML file path on the 4th line and try it.
namespace ConsoleApplication1
{
class Program
{
//private const string xmlPath = #"C:\Users\Jumast\Desktop\StackOverflowQuestion.xml";
private const string xmlPath, // put the file path here
static Body makeBody()
{
var instance1 = new MyType() { Category = Category.MyTypeA, Foo = "bar" };
var instance2 = new MyType() { Category = Category.MyTypeB, Foo = "bar" };
return new Body(){Instance1 = instance1, Instance2 = instance2};
}
static void serializeBody(Body body, string path)
{
var ser = new DataContractSerializer(body.GetType(), body.GetType().Name, "");
using (var w = XmlWriter.Create(path, new XmlWriterSettings() { Indent = true }))
{
ser.WriteObject(w, body);
}
}
static Body deseerializeBody(string xmlPath)
{
Body deserializedBody;
var ser = new XmlSerializer(typeof(Body));
using (Stream stream = File.OpenRead(xmlPath))
{
deserializedBody = (Body)ser.Deserialize(stream);
}
return deserializedBody;
}
static void writeBodyToConsole(Body body)
{
Console.WriteLine("Instance1: " + body.Instance1);
Console.WriteLine("Instance2: " + body.Instance2);
Console.ReadKey();
}
static void Main(string[] args)
{
serializeBody(makeBody(), xmlPath);
writeBodyToConsole(deseerializeBody(xmlPath));
}
}
public class Body : IXmlSerializable
{
#region Properties
public MyType Instance1 { get; set; }
public MyType Instance2 { get; set; }
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Instance1 = new MyType(reader);
Instance2 = new MyType(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Instance1.WriteXml(writer);
Instance2.WriteXml(writer);
}
#endregion
}
public class MyType : IXmlSerializable
{
#region Fields
private Category _category;
#endregion
#region Constructors
public MyType()
{
_category = Category.nil;
Foo = string.Empty;
}
public MyType(XmlReader reader) { ReadXml(reader);}
#endregion
#region Methods
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(string.Format("Foo = {0}", Foo));
sb.Append(" , ");
sb.Append(string.Format("Category = {0}", Category));
return sb.ToString();
}
#endregion
#region Properties
public string Foo { get; set; }
public Category Category
{
get { return this._category; }
set { this._category = value; }
}
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
Enum.TryParse(reader.Name, out _category);
reader.Read();
Foo = reader.ReadElementContentAsString("Foo", "");
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(this.Category.ToString(), "");
writer.WriteElementString("Foo", Foo);
writer.WriteEndElement();
}
#endregion
}
public enum Category
{
MyTypeA,
MyTypeB,
nil
}
}

Categories