I'm attempting to serialize (and subsequently deserialize) a rather simple class to an XML string, but am getting an exception: "The type System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] may not be used in this context."
The method I'm using to serialize is:
public string ToXml(TaskListFilterConfig config)
{
Type[] extraTypes = { typeof(FilterConfig), typeof(SortConfig) };
XmlSerializer serializer = new XmlSerializer(config.GetType(), extraTypes);
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, config);
return writer.ToString();
}
}
The classes I'm attempting to serialize are:
[XmlRoot(ElementName = "TaskListFilterConfig", IsNullable = false)]
[XmlInclude(typeof(FilterConfig))]
[XmlInclude(typeof(SortConfig))]
public class TaskListFilterConfig
{
[XmlArray("FilterConfigList")]
[XmlArrayItem("FilterConfig")]
public List<FilterConfig> FilterConfigList { get; set; }
[XmlArray("SortConfigList")]
[XmlArrayItem("SortConfig")]
public List<SortConfig> SortConfigList { get; set; }
public TaskListFilterConfig()
{
FilterConfigList = new List<FilterConfig>();
SortConfigList = new List<SortConfig>();
}
}
[XmlType("FilterConfig")]
public class FilterConfig
{
public OperandType Operand { get; set; }
public int SelectedOperatorIndex { get; set; }
public int SelectedColumnIndex { get; set; }
public object RightOperand { get; set; }
public FilterConfig() { }
}
[XmlType("SortConfig")]
public class SortConfig
{
public Infragistics.Windows.Controls.SortStatus SortDirection { get; set; }
public int ColumnSelectedIndex { get; set; }
public SortConfig() { }
}
Your Class Model and Searalizable data are wrong.
Notice that TaskListFilterConfig is a different type than extraTypes. extraTypes has some other model data.
If you want to serialize TaskListFilterConfig class and Data: Use below Code
public string ToXml(TaskListFilterConfig config)
{
XmlSerializer serializer = new XmlSerializer(typeOf(TaskListFilterConfig));
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, config);
return writer.ToString();
}
}
Or if you want extraTypes pass the relevent model data as well.
I know it's a very late answer. Just want to make sure the question has answered. Maybe it help will someone in the future. :)
Related
I'm trying to set up a very small database using XML serialization and more specifically XmlSerializer.
My main class is the following :
public class XmlDB
{
[XmlIgnore]
public string FilePath { get; private set; }
public List<FooType> Foos { get; set; }
public List<BarType> Bars { get; set; }
public List<ThirdType> Thirds { get; set; }
private XmlDB():this(null) { }
public XmlDB(string strDBPath) {
this.FilePath = strDBPath;
this.Foos = new List<FooType>();
this.Bars = new List<BarType>();
this.Thirds = new List<ThirdType>();
}
public static XmlDB Load(string strDBPath) {
using (XmlReader reader = XmlReader.Create(strDBPath)) {
XmlDB db = (XmlDB)new XmlSerializer(typeof(XmlDB)).Deserialize(reader);
db.FilePath = strDBPath;
return db;
}
}
public void SaveChanges() {
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create(this.FilePath, settings)) {
XmlSerializer ser = new XmlSerializer(typeof(XmlDB));
ser.Serialize(writer, this);
}
}
}
My test method creates an instance, populates the lists and calls the SaveChanges method.
Everything works fine on serialization and the Xml output looks consistent.
The problem happens on deserializing : No error is reported but only the first item of the first List is treated, the following items of the first list are not deserialized, neither are the following lists...
If I shuffle the order of the lists in the Xml, it's always the first item of the first list in the Xml file that is deserialized.
I tried the following simple test to confirm (which unfortunately works fine, all lists are populated on deserializing) :
public class DBTestList
{
public List<DBTest> TestList { get; set; }
public List<DBTest2> TestList2 { get; set; }
public DBTestList() {
this.TestList = new List<DBTest>();
this.TestList2 = new List<DBTest2>();
}
}
public class DBTest
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public class DBTest2
{
public int TestInt { get; set; }
public string TestStr { get; set; }
}
public void TestSerialProblem() {
//Init data
DBTestList tl = new DBTestList();
tl.TestList.Add(new DBTest() { TestInt = 1, TestStr = "test11" });
tl.TestList.Add(new DBTest() { TestInt = 2, TestStr = "test12" });
tl.TestList2.Add(new DBTest2() { TestInt = 3, TestStr = "test21" });
XmlWriterSettings settings = new XmlWriterSettings() {
Indent = true,
Encoding = Encoding.UTF8
};
using (XmlWriter writer = XmlWriter.Create("test.db", settings)) {
XmlSerializer ser = new XmlSerializer(typeof(DBTestList));
ser.Serialize(writer, tl);
}
using (XmlReader reader = XmlReader.Create("test.db")) {
DBTestList db = (DBTestList)new XmlSerializer(typeof(DBTestList)).Deserialize(reader);
Assert.IsTrue(db.TestList2[0].TestStr == "test21");
}
}
I read a lot of posts on this subject but none helped.
Do you have an idea ?
Thanks,
Best regards.
EDIT :
To give a more detailed idea of the classes used in the lists, here's one basic implementation.
All the types are derived from the parent one a_SolidElement, adding only a few properties (basic value types and/or enum) :
public abstract class a_SolidElement
{
[XmlIgnore]
public int Position { get; set; }
public virtual double Thickness { get; set; }
public virtual double Density { get; set; }
public string SupplierName { get; set; }
public string Name { get; set; }
}
public enum ElementType
{
Undefined=0,
TypeA,
TypeB
}
public class FooType:a_SolidElement
{
public double AdditionalData { get; set; }
public e_ElementType ElementType { get; set; }
}
dbc was actually right about the bad IXmlSerializable implementation in his comment :
In one of my classes, I had one property of a type I didn't write, with a problem in the readXml method.
I didn't see it at first because I successively removed some properties to see which one caused the problem but this one was still in the first deserialized item so that even if the subsequent ones didn't have it, the reader was still already messed up by the first one.
It's so obvious now that I feel bad for asking the question in the first place !
Thank you very much for the help !
So, - in my DocumentDB I may have the following document:
{
"id": 1,
"type": "A",
"content": {
"x": 1,
"y": 2
}
}
That may be backed by this model:
public class acontent
{
public int x { get; set; }
public int y { get; set; }
}
public class document
{
public int id { get; set; }
public string type { get; set; }
public object content { get; set; }
}
public class documenta : document
{
public new acontent content { get; set; }
}
The idea here is that document is a complex object where content may vary depending on type.
Now, - in my ServiceFabric application I have a stateless microservice that reads from DocumentDB and should return a document type object when called from the ServiceProxy.
The problem in this is that the DocumentQuery from the DocumentDB SDK, uses Json.NET serializer when querying the database, whilst servicefabric uses DataContractSerializer for serializing the service-messages.
So when the content part of document class is being deserialized from the DocumentDB it becomes:
Newtonsoft.Json.Linq.JObject
But when it is serialized back through the returned service-message you get the exception:
Type 'Newtonsoft.Json.Linq.JToken' is a recursive collection data
contract which is not supported. Consider modifying the definition of
collection 'Newtonsoft.Json.Linq.JToken' to remove references to
itself.
To illustrate this issue try the folowing code:
using System;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
using Newtonsoft.Json;
namespace jsoinissue
{
public class acontent
{
public int x { get; set; }
public int y { get; set; }
}
public class document
{
public int id { get; set; }
public string type { get; set; }
public object content { get; set; }
}
public class documenta : document
{
public new acontent content { get; set; }
}
public class Program
{
private const string JSON_A = "{\"id\":1,\"type\":\"A\",\"content\":{\"x\":1,\"y\":2}}";
private static string SerializeObject<T> (T obj)
{
try
{
DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(T));
using (var ms = new MemoryStream())
{
js.WriteObject(ms, obj);
ms.Position = 0;
using (var sr = new StreamReader(ms))
return sr.ReadToEnd();
}
}
catch (Exception e)
{
return String.Format("EXCEPTION: {0}",e.Message);
}
}
public static void Main()
{
var A = JsonConvert.DeserializeObject<document>(JSON_A);
var a = SerializeObject<document>(A);//HERE BE TROUBLE
Console.WriteLine(a);
Console.ReadKey();
}
}
}
How could I best resolve this issue?
Your basic problem is that DataContractJsonSerializer does not support untyped, free-form JSON data. As explained in Working with untyped JSON in a WCF service the System.Json namespace was added to Silverlight for this purpose, but it seems that it never made it into the full .Net class library.
Instead, in your stateless microservice can do a nested serialization where the free-form JSON is represented as an escaped string literal when serializing using the data contract serializer. Thus your classes would look something like this:
[DataContract]
[JsonObject]
public abstract class documentbase
{
[DataMember]
[JsonProperty]
public int id { get; set; }
[DataMember]
[JsonProperty]
public string type { get; set; }
[IgnoreDataMember]
[JsonProperty("content")]
public abstract JToken JsonContent { get; set; }
[JsonIgnore]
[DataMember(Name = "content")]
string DataContractContent
{
get
{
if (JsonContent == null)
return null;
return JsonContent.ToString(Newtonsoft.Json.Formatting.None);
}
set
{
if (string.IsNullOrEmpty(value))
JsonContent = null;
else
JsonContent = JToken.Parse(value);
}
}
}
[DataContract]
[JsonObject]
public class document : documentbase
{
JToken content;
public override JToken JsonContent { get { return content; } set { content = value; } }
}
[DataContract]
[JsonObject]
public class document<T> : documentbase where T : class
{
[IgnoreDataMember]
[JsonIgnore]
public T Content { get; set; }
public override JToken JsonContent
{
get
{
if (Content == null)
return null;
return JToken.FromObject(Content);
}
set
{
if (value == null || value.Type == JTokenType.Null)
Content = null;
else
Content = value.ToObject<T>();
}
}
}
Then the JSON generated by SerializeObject<document>(A) will look like:
{
"content":"{\"x\":1,\"y\":2}",
"id":1,
"type":"A"
}
Then, on the receiving system, you can deserialize to a document using the data contract serializer, then query the deserialized JToken JsonContent with LINQ to JSON. Alternatively, if the receiving system knows to expect a document<acontent> it can deserialize the data contract JSON as such, since document and document<T> have identical data contracts.
Have you looked into changing away from DataContractSerializer to a serializer with better support instead? Here's how you'd plug in a different serializer.
class InitializationCallbackAdapter
{
public Task OnInitialize()
{
this.StateManager.TryAddStateSerializer(new MyStateSerializer());
return Task.FromResult(true);
}
public IReliableStateManager StateManager { get; set; }
}
class MyStatefulService : StatefulService
{
public MyStatefulService(StatefulServiceContext context)
: this(context, new InitializationCallbackAdapter())
{
}
public MyStatefulService(StatefulServiceContext context, InitializationCallbackAdapter adapter)
: base(context, new ReliableStateManager(context, new ReliableStateManagerConfiguration(onInitializeStateSerializersEvent: adapter.OnInitialize)))
{
adapter.StateManager = this.StateManager;
}
}
This could be newtonsoft or whatever. Also I believe that the method is currently marked "Deprecated" however there's no alternative, so if it solves your problem go ahead and use it.
I know there are several posts out there with this topic, but I can't seem to figure out what is the problem here. I have serialized and deserialized xml several times, and never had this error.
The exception message is:
There is an error in XML document (1, 2).
With InnerException:
<InvoiceChangeRequest xmlns=''> was not expected.
XML file I want to deserialize:
<ns1:InvoiceChangeRequest xmlns:ns1="http://kmd.dk/fie/external_invoiceDistribution">
<CONTROL_FIELDS>
<STRUCTURID>0000000001</STRUCTURID>
<OPERA>GET</OPERA>
<WIID>000050371220</WIID>
</CONTROL_FIELDS>
<HEADER_IN>
<MANDT>751</MANDT>
<BELNR>1234567890</BELNR>
</HEADER_IN>
<ITEMS>
<ITEM_FIELDS_IN>
<BUZEI>001</BUZEI>
<BUKRS>0020</BUKRS>
</ITEM_FIELDS_IN>
</ITEMS>
</ns1:InvoiceChangeRequest>
Class I'm trying to deserialize to:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")] public ControlFields Styrefelter;
[XmlElement("HEADER_IN")] public HeaderIn HeaderfelterInd;
[XmlElement("ITEMS")] public Items Linjer;
}
public class HeaderIn
{
[XmlElement("MANDT")] public string Kommunenummer;
[XmlElement("BELNR")] public string RegnskabsbilagsNummer;
}
public class Items
{
[XmlElement("ITEM_FIELDS_IN")] public Itemfield[] ItemfelterInd;
}
public class Itemfield
{
[XmlElement("BUZEI")] public string Linjenummer;
[XmlElement("BUKRS")] public string Firmakode;
}
Deserialization code:
XmlSerializer serializer = new XmlSerializer(typeof(InvoiceChangeRequest));
var request = serializer.Deserialize(new StringReader(output)) as InvoiceChangeRequest;
In your XML file your root element is the namespace http://kmd.dk/fie/external_invoiceDistribution with prefix ns1.
The element <CONTROL_FIELDS> isn't because it isn't prefixed. Your serialization class doesn't take this into account though. That means that it expects that <CONTROL_FIELDS> and the other elements are ALSO in the ns1 namespace.
To get the serializer parse the elements correctly add the Namespace to the elements, setting it to an empty string:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS", Namespace = "")]
public ControlFields Styrefelter { get; set; }
[XmlElement("HEADER_IN", Namespace = "")]
public HeaderIn HeaderfelterInd { get; set; }
[XmlElement("ITEMS", Namespace = "")]
public Items Linjer { get; set; }
}
This will de-serialize the given XML as intended.
In case of de-serialization issues I often create the classes in memory and then serialize that so I can inspect the resulting XML. That often gives clues on what is missing or being added compared to the input document:
var ms = new MemoryStream();
serializer.Serialize(ms, new InvoiceChangeRequest {
Styrefelter = new ControlFields { Opera="test"}
});
var s = Encoding.UTF8.GetString(ms.ToArray());
And then inspect s for differences.
You can replace 'ns1:' with string.Empty.
Below classes should serialize.
public class Item
{
[XmlElement("BUZEI")]
public string Buzei { get; set; }
[XmlElement("BUKRS")]
public string Bukrs { get; set; }
}
public class Header
{
[XmlElement("MANDT")]
public string Mandt { get; set; }
[XmlElement("BELNR")]
public string Belnr { get; set; }
}
public class ControlFields
{
[XmlElement("STRUCTURID")]
public string StructuredId { get; set; }
[XmlElement("OPERA")]
public string Opera { get; set; }
[XmlElement("WIID")]
public string Wild { get; set; }
}
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")]
public ControlFields ControlFields { get; set; }
[XmlElement("HEADER_IN")]
public Header Header { get; set; }
[XmlArray("ITEMS")]
[XmlArrayItem("ITEM_FIELDS_IN")]
public List<Item> Items { get; set; }
}
Here's the XML file I'm trying to deserialize:
<?xml version="1.0" encoding="utf-8"?>
<EntityType>
<Name>SomeName</Name>
<Components>
<ComponentAssembly>Some assembly name</ComponentAssembly>
</Components>
</EntityType>
And here is the Data contract I am using to deserialize it:
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace GameUtilities.Entities.DataContracts
{
[DataContract(Name="EntityType",Namespace="")]
public class EntityTypeData
{
[DataMember(IsRequired=true,Order = 0)]
public string Name { get; private set; }
[DataMember(IsRequired=false,Order=1)]
public List<ComponentEntry> Components { get; private set; }
public EntityTypeData(string name, List<ComponentEntry> components = null)
{
Name = name;
if(components == null)
{
Components = new List<ComponentEntry>();
}
else
{
Components = components;
}
}
}
[DataContract]
public class ComponentEntry
{
[DataMember(IsRequired = true, Order = 0)]
public string ComponentAssembly { get; private set; }
public ComponentEntry(string componentAssembly)
{
ComponentAssembly = componentAssembly;
}
}
}
Deserializing it works correctly, but the Components list is always empty, no matter how many entrys I put inside the tags. I have tried marking the [DataMemeber] attribute for Components as "IsRequired=true", and deserialization still completes without error, but the List is not getting populated. Can you see any issues with my data contract that would make this fail?
EDIT: As a test, I ran an object using the Data Contract above through a serializer to see what XML got spat out. Here's what I saw:
<EntityType xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>TESTNAME</Name>
<Components xmlns:a="http://schemas.datacontract.org/2004/07/GameUtilities.Entities.DataContracts">
<a:ComponentEntry>
<a:ComponentAssembly>ONE</a:ComponentAssembly>
</a:ComponentEntry>
<a:ComponentEntry>
<a:ComponentAssembly>TWO</a:ComponentAssembly>
</a:ComponentEntry>
<a:ComponentEntry>
<a:ComponentAssembly>THREE</a:ComponentAssembly>
</a:ComponentEntry>
</Components>
Here is the serialization code I used:
public static void SerializeObject<T>(string path, T obj)
{
FileStream fs = new FileStream(path,FileMode.Create);
XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs);
DataContractSerializer ser = new DataContractSerializer(typeof(T));
//Serialize the data to a file
ser.WriteObject(writer, obj);
writer.Close();
fs.Close();
}
As you can see, there is a separate ComponentEntry being created for each ComponentAssembly that is listed. Is there a way to get rid of that and just get the ComponentAssembly?
Easiest solution would be to define a collection data contract instead. I don't think there is a way to have the ComponentEntry as a separate type, when using DataContractSerializer.
[DataContract(Name="EntityType",Namespace="")]
public class EntityTypeData
{
[DataMember(IsRequired=true,Order=0)]
public string Name { get; private set; }
[DataMember(IsRequired=false,Order=1)]
public ComponentList Components { get; private set; }
public EntityTypeData(string name, IEnumerable<string> components = null)
{
Name = name;
Components = new ComponentList();
if(components != null)
{
Components.AddRange(components);
}
}
}
[CollectionDataContract(ItemName = "ComponentAssembly", Namespace="")]
public class ComponentList : List<string> {}
var ser = new DataContractSerializer(typeof(EntityTypeData));
var entity = (EntityTypeData) ser.ReadObject(stream);
If i have objects with properties of type object or objects that are generics, how can i serialize this?
Eg.
public class MyClass
{
public Object Item { get; set; }
}
or
public class MyClass<T>
{
public T Item { get; set; }
}
EDIT
My Generic class now looks like this:
public class MyClass<T>
{
public MySubClass<T> SubClass { get; set; }
}
public class MySubClass<T>
{
public T Item { get; set; }
}
Additonal question: How can i change the element name for Item at runtime to typeof(T).Name?
Have you tried the [Serializable] attribute?
[Serializable]
public class MySerializableClass
{
public object Item { get; set; }
}
[Serializable]
public class MySerializableGenericClass<T>
{
public T Item { get; set; }
}
Although the generic class is only serializable if the generic type parameter is serializable as well.
Afaik there is no way to constrain the type parameter to be serializable. But you can check at runtime using a static constructor:
[Serializable]
public class MySerializableGenericClass<T>
{
public T Item { get; set; }
static MySerializableGenericClass()
{
ConstrainType(typeof(T));
}
static void ConstrainType(Type type)
{
if(!type.IsSerializable)
throw new InvalidOperationException("Provided type is not serializable");
}
}
Use these methods to serialize\deserialize any object (even generics) to an XML file, though can be modified to suit other purposes:
public static bool SerializeTo<T>(T obj, string path)
{
XmlSerializer xs = new XmlSerializer(obj.GetType());
using (TextWriter writer = new StreamWriter(path, false))
{
xs.Serialize(writer, obj);
}
return true;
}
public static T DeserializeFrom<T>(string path)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
using (TextReader reader = new System.IO.StreamReader(path))
{
return (T)xs.Deserialize(reader);
}
}