When I'm deserializing a xml string, I need to save a XElement outerXml on a string property called prop2.
My XML:
<MyObj>
<prop1>something</prop1>
<prop2>
<RSAKeyValue>
<Modulus>...</Modulus>
<Exponent>...</Exponent>
</RSAKeyValue>
</prop2>
<prop3></prop3>
</MyObj>
My object:
public class MyObj
{
[XmlElement("prop1")]
public string prop1 { get; set; }
[XmlText]
public string prop2 { get; set; }
[XmlElement(ElementName = "prop3", IsNullable = true)]
public string prop3 { get; set; }
}
I'm deserializing using XmlSerializer, like this:
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(new StringReader(myXmlString));
I tried to use [XmlText] to save XML text in prop2 But I'm only getting null.
What I need to do to save <RSAKeyValue><Modulus>...</Modulus><Exponent>...</Exponent></RSAKeyValue> like text in prop2?
XmlText will give value as XML encoded as text (">prop2<...") see XmlTextAttribute
By default, the XmlSerializer serializes a class member as an XML element. However, if you apply the XmlTextAttribute to a member, the XmlSerializer translates its value into XML text. This means that the value is encoded into the content of an XML element.
One possible solution - use XmlNode as type of the property:
public class MyObj
{
[XmlElement("prop1")]
public string prop1 { get; set; }
public XmlNode prop2 { get; set; }
[XmlElement(ElementName = "prop3", IsNullable = true)]
public string prop3 { get; set; }
}
var r = (MyObj)serializer.Deserialize(new StringReader(myXmlString));
Console.WriteLine(r.prop2.OuterXml);
Alternatively you may make whole object implement custom Xml serialization or have custom type that matches XML (to read normally) and have additional property to represent that object as XML string.
Related
I have the following test XML string:
<?xml version="1.0" encoding="UTF-8"?>
<test id="myid">
<b>b1</b>
<a>a2</a>
<a>a1</a>
<b>b2</b>
</test>
which I deserialize using this class:
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement(ElementName = "a")]
public List<string> A { get; set; }
[XmlElement(ElementName = "b")]
public List<string> B { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
If I'm now going to serialize the object the result will be:
<?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" id="myid">
<a>a2</a>
<a>a1</a>
<b>b1</b>
<b>b2</b>
</test>
Is there a way to keep the initial sort order?
I guess I can't use [XmlElementAttribute(Order = x)] cause the order shouldn't be hardcoded but identically with the initial xml.
I thought about adding an order property to my lists like that
[XmlRoot(ElementName="a")]
public class A
{
[XmlAttribute(AttributeName="order")]
public string Order { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName="b")]
public class B
{
[XmlAttribute(AttributeName="order")]
public string Order { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName="test")]
public class Test
{
[XmlElement(ElementName="a")]
public List<A> A { get; set; }
[XmlElement(ElementName="b")]
public List<B> B { get; set; }
[XmlAttribute(AttributeName="id")]
public string Id { get; set; }
}
but I don't know how to order by them when serializing.
You can do this with XmlSerializer by using a single collection to capture both the <a> and <b> elements and applying the [XmlElement(Name, Type = typeof(...))] attribute to it multiple times, once for each desired element name. Because you are using a single collection to deserialize both elements, the order is automatically preserved. However, to make this work, XmlSerializer must be able to determine the correct element name when re-serializing. There are two approaches to accomplish this, as documented in Choice Element Binding Support:
If the collection contains polymorphic items, the element name can be mapped to the concrete item type by using the [XmlElementAttribute(String, Type)] constructor. For instance, if you have a sequence of elements that might be strings or integers like so:
<Things>
<string>Hello</string>
<int>999</int>
</Things>
This can be bound to a collection as follows:
public class Things
{
[XmlElement(Type = typeof(string)),
XmlElement(Type = typeof(int))]
public List<object> StringsAndInts { get; set; }
}
If the collection contains only a single type of item, the element name can be encoded in an associated array of enum values, where the enum names correspond to the element names and the array itself is identified via the [XmlChoiceIdentifierAttribute] attribute.
For details, see the documentation examples.
I find option #1 easier to work with than option #2. Using this approach, the following model will deserialize and re-serialize your XML while successfully preserving the order of the <a> and <b> elements:
public abstract class StringElementBase
{
[XmlText]
public string Text { get; set; }
public static implicit operator string(StringElementBase element)
{
return element == null ? null : element.Text;
}
}
public sealed class A : StringElementBase
{
}
public sealed class B : StringElementBase
{
}
[XmlRoot(ElementName = "test")]
public class Test
{
[XmlElement("a", Type = typeof(A))]
[XmlElement("b", Type = typeof(B))]
public List<StringElementBase> Items { get; } = new List<StringElementBase>();
[XmlIgnore]
// For convenience, enumerate through the string values of the items.
public IEnumerable<string> ItemValues { get { return Items.Select(i => (string)i); } }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
Working .Net fiddle.
For more examples of using [XmlChoiceIdentifier] to deserialize a sequence of elements with different names to c# objects of the same type, see for instance here or here.
No, basically; XmlSerializer doesn't support that. If you want to use that option you'd need to write it manually with XDocument or XmlDocument or XmlReader.
I am having trouble with deserializing a XML into C# Model. I am pulling that XML from external API, so i have no control over it. It has multiple "InnerText" nodes, that serialized cant deserialize correctly(last one wins, orhers are lost).
I`ve already fixed this using XmlDocument Class, but i need to do it with model instead.
The XML i am trying to deserialize:
<root>
<passage>
<hlword>Test</hlword>
your Internet.....
<hlword>test</hlword>
from Ookla.
</passage>
</root>
C# Classes:
[XmlRoot(ElementName = "passage")]
public class Passage
{
[XmlElement(ElementName = "hlword")]
public List<string> Hlword { get; set; }
[XmlText]
public string InnerText { get; set; }
}
[XmlRoot(ElementName = "root")]
public class Root
{
[XmlElement(ElementName = "passage")]
public List<Passage> Passage { get; set; }
[XmlText]
public string InnerText { get; set; }
}
From example above, i need to extract: "Test your internet..... test from Ookla", Instead I am getting List of Passage class with 2 Hlwords (inner text "test") and inner text on Root class "from Ookla". All the text after elements is ommited.
Try reading the innertext using XmlDocument
var doc = new XmlDocument();
doc.LoadXml(xmlString);
var text = doc.SelectSingleNode("root/passage").InnerText;
I am deserializing a xml file with an attribute and a xml text. The problem is that those elements have the same attributes. So I am always getting the error that I must not have two identical TypeNames in XmlType.
My xml:
<group_id xsi:type="xsd:int">1</group_id>
<name xsi:type="xsd:int">myNameView</name>
And my C#:
[XmlType(AnonymousType = true, Namespace = "http://www.w3.org/2001/XMLSchema", TypeName = "int")]
[XmlRoot(ElementName = "group_id")]
public class Group_id
{
[XmlAttribute(AttributeName = "type", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Type { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://www.w3.org/2001/XMLSchema", TypeName = "int")]
[XmlRoot(ElementName = "name")]
public class Name
{
[XmlAttribute(AttributeName = "type", Namespace = "http://www.w3.org/2001/XMLSchema-instance")]
public string Type { get; set; }
[XmlText]
public string Text { get; set; }
}
The problem is the TypeName in XmlType Attribute. If I name only one element with a TypeName it is correctly deserialized.
XmlSerializer handles types for you, based on the xsi:type attribute. By trying to handle these yourself, you're causing it some grief.
If you declare your elements as object, then the serializer will use the type attributes to determine how to deserialize the values. Note as your example is just a fragment, I've assumed root element called root:
[XmlRoot("root")]
public class Root
{
[XmlElement("group_id")]
public object GroupId { get; set; }
[XmlElement("name")]
public object Name { get; set; }
}
Now when you deserialize your example, you'd actually get an exception (as myNameView isn't an integer). You can fix by either changing the type to xsd:string or changing the value to a valid integer.
In the event the XML was valid, you'd see that the types deserialized map directly to the type attributes. See this fiddle for a working demo.
There have been a number of questions about serializing and deserializing arrays using DataContractJsonSerializer (including one from me: How can I serialise a string array to JSON using DataContractJsonSerializer?) but none seem to answer the current problem that I'm having.
I am converting an XML string to a JSON string by deserializing the XML to a DataContract object and then serializing that object to JSON, using DataContractJsonSerializer. My approach is the same as I have used on a number of other objects, all of which are serializing to JSON perfectly, but I have an object in which an array property is always rendered as null after serialization.
The classes are defined as follows:-
[DataContract]
public class Order
{
[DataMember(Name = "consignments")]
[XmlElement("consignments")]
public Consignment[] Consignments { get; set; }
}
[DataContract]
public class Consignment
{
[DataMember(Name = "conh_id")]
[XmlElement("conh_id")]
public string ConsignmentHeaderId { get; set; }
[DataMember(Name = "conh_status")]
[XmlElement("conh_status")]
public string ConsignmentHeaderStatus { get; set; }
[DataMember(Name = "consignmententries")]
[XmlElement("consignmententries")]
public ConsignmentEntry[] ConsignmentEntries { get; set; }
}
The XML I'm using looks like this:-
<order>
<consignments>
<consignment>
<conh_id>A19708176</conh_id>
<conh_status>ACCEPTED</conh_status>
<consignmententries>
<consignmententry>
<conl_lineNbr>10000</conl_lineNbr>
<conl_sku>SEC01XXZBUXXX</conl_sku>
<conl_original_qty>1</conl_original_qty>
</consignmententry>
</consignmententries>
</consignment>
</consignments>
</order>
The deserializing and serializing is done in the following methods:-
private object DeserialiseXml(string xml)
{
var serialiser = new XmlSerializer(typeof(Order));
var stringReader = new StringReader(xml);
var result = serialiser.Deserialize(stringReader);
return result;
}
private string SerialiseJson(object serialisable)
{
using (MemoryStream stream = new MemoryStream())
{
var serialiser = new DataContractJsonSerializer(serialisable.GetType());
serialiser.WriteObject(stream, serialisable);
var json = Encoding.UTF8.GetString(stream.ToArray());
return json;
}
}
When I test it, the Consignments property is always null in the resulting JSON.
"order": {
"consignments": [
{
"conh_id": null,
"conh_status": null,
"consignmententries": null
}
]
}
Debugging shows that this property is null in the object created after the deserialization step so the problem is in the XML deserialization rather than the JSON serialization.
What do I need to change on my object model to get the array converted properly?
Your problem is happening when you deserialize from XML rather than when you serialize to JSON. In your XML, the collections have been serialized with two levels: an outer container element and an element for each item:
<consignments>
<consignment>
<!-- Consignment data -->
</consignment>
</consignments>
And
<consignmententries>
<consignmententry>
<!-- Entry data -->
</consignmententry>
</consignmententries>
(For comparison, in the linked question the Labels XML collection has a single level). Thus you need to use [XmlArray(string name)] and [XmlArrayItem(string itemName)] to specify the outer and inner element names:
[DataContract]
[XmlRoot("order")]
public class Order
{
[DataMember(Name = "consignments")]
[XmlArray("consignments")]
[XmlArrayItem("consignment")]
public Consignment[] Consignments { get; set; }
}
[DataContract]
public class Consignment
{
[DataMember(Name = "conh_id")]
[XmlElement("conh_id")]
public string ConsignmentHeaderId { get; set; }
[DataMember(Name = "conh_status")]
[XmlElement("conh_status")]
public string ConsignmentHeaderStatus { get; set; }
[DataMember(Name = "consignmententries")]
[XmlArray("consignmententries")]
[XmlArrayItem("consignmententry")]
public ConsignmentEntry[] ConsignmentEntries { get; set; }
}
Conversely, adding the attribute [XmlElement] to a collection tells XmlSerializer that the collection should be serialized without the outer container element.
You also need to add [XmlRoot("order")] to Order to specify the root element name.
Now your XML will deserialize successfully.
I'm using the .NET XmlSerializer class to deserialize some XML document. In that document, I have an element that looks like that:
<MyElement attr1="xxx" attr2="yyy">VALUE</MyElement>
This is a part of a bigger XML. I need to deserialize this string into an object, so I wrote:
public class MyElement
{
[XmlAttribute(AttributeName = "attr1")]
public string attr1 { get; set; }
[XmlAttribute(AttributeName = "attr2")]
public string attr2 { get; set; }
[??????????????]
public string value { get; set; }
}
And I can't figure out what to put instead of the question marks in order to get the value of the element into the value.
XmlTextAttribute:
By default, the XmlSerializer serializes a class member as an XML element. However, if you apply the XmlTextAttribute to a member, the XmlSerializer translates its value into XML text. This means that the value is encoded into the content of an XML element.
[XmlText]
public string Value { get; set; }
If the XmlTextAttribute does not work, you could use the XmlElementAttribute
[XmlElement( DataType = "string", ElementName = "value" )]
public string value { get; set; }