using generics with xml deserialisation - c#

I want to pass an object into the resulting type of an xml deserialisation and maintain strong typing.
So the deserialisation class can take any type that implements the IResult interface which in this case is Result and Result2.
I have got this working by making the getObject method return dynamic but i would much rather keep the compile time checking and i think it should be possible.
I have tried using generics, as in the example below, but the deser.getObject(doc()); line gives me a "cannot be inferred from usage" compile error.
Thanks for all help.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace SOQuestion
{
class Program
{
static void Main(string[] args)
{
var deser = new Deserialised(new Result());
var result = deser.getObject(doc());
var deser2 = new Deserialised(new Result2());
var result2 = deser.getObject(doc());
Console.Writeline(result.status);
Console.Writeline(result2.status);
}
public XmlDocument doc()
{
var doc = new XmlDocument();
var el = (XmlElement)doc.AppendChild(doc.CreateElement("Result"));
el.SetAttribute("status", "ok");
el.SetAttribute("status2", "not ok");
return doc;
}
}
class Deserialised
{
private IResult result;
private Type resultType;
public Deserialised(IResult _result)
{
result = _result;
resultType = Type.GetType(result.GetType().AssemblyQualifiedName);
}
public T getObject<T>(XmlDocument xml)
{
var mySerializer = new XmlSerializer(resultType);
var myStream = new MemoryStream();
xml.Save(myStream);
myStream.Position = 0;
var r = mySerializer.Deserialize(myStream);
return (T)r;
}
}
interface IResult
{
public string status {get;set;}
}
[Serializable]
public class Result :IResult
{
[XmlAttribute]
public string status { get; set; }
}
[Serializable]
public class Result2 : IResult
{
[XmlAttribute]
public string status2 { get; set; }
}
}

Indeed, that isn't going to work - the compiler has no way of knowing the T from that. Remember that the T comes from the caller at compile-time, not from the result of the method at runtime. There are ways to switch between reflection/generics, but it is ugly and isn't going to help much here. I would just return object instead:
public object GetObject(XmlDocument xml) {
var mySerializer = new XmlSerializer(resultType);
using(var myStream = new MemoryStream()) {
xml.Save(myStream);
myStream.Position = 0;
return mySerializer.Deserialize(myStream);
}
}
and then let the caller handle the dynamic etc:
var deser = new Deserialised(new Result());
dynamic result = deser.GetObject(doc());
var deser2 = new Deserialised(new Result2());
dynamic result2 = deser.GetObject(doc());
Console.Writeline(result.status);
Console.Writeline(result2.status);
Because of the dynamic above, the .status in the two Console.WriteLine will still work.

In my opinion the answer you provided yourself overcomplicates things. (In addition to doing some other weird stuff, ... see my comment.)
There is nothing gained in using generics here. You can just as well use the following approach, and both of your requirements will still hold.
public IResult GetObject(XmlDocument xml)
{
var mySerializer = new XmlSerializer(resultType);
using (var myStream = new MemoryStream())
{
xml.Save(myStream);
myStream.Position = 0;
return (IResult)mySerializer.Deserialize(myStream);
}
}
... simply call it as follows:
var deser = new Deserialised(new Result());
var result = (Result)deser.getObject(doc());
var deser2 = new Deserialised(new Result2());
var result2 = (Result2)deser.getObject(doc());
Casting to anything which doesn't implement IResult will trigger a compiler error.
It is not really clear what you are trying to do here, but suppose you made Deserialised generic.
class Deserialised<T>
where T : IResult
{
private T result;
private Type resultType;
public Deserialised(T _result)
{
result = _result;
}
public T getObject(XmlDocument xml)
{
var mySerializer = new XmlSerializer(typeof(T));
var myStream = new MemoryStream();
xml.Save(myStream);
myStream.Position = 0;
var r = (T)mySerializer.Deserialize(myStream);
return r;
}
}
Why are you even passing _result as a parameter and storing it? My guess is you only needed it since you didn't know about typeof()? In that case simply drop it. After doing so again you just end up with a class which defines your generic parameter, with again the sole purpose to define the required cast.

So i have found a solution that my two requirements of 1. that passed in type implements IResult interface 2. that the returned object is statically typed.
A generic method with a constraint like below
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace SOQuestion
{
class Program
{
static void Main(string[] args)
{
var result = new Deserialised().getObject<Result>();
var result2 = new Deserialised().getObject<Result2>();
Console.WriteLine(result.status);
Console.WriteLine(result.errorMessage);
Console.ReadLine();
}
}
class Deserialised
{
public T getObject<T>() where T : IResult
{
try
{
var instance = Activator.CreateInstance<T>();
var mySerializer = new XmlSerializer(instance.GetType());
var myStream = new MemoryStream();
doc().Save(myStream);
myStream.Position = 0;
var r = mySerializer.Deserialize(myStream);
throw new DivideByZeroException();
return (T)r;
}
catch (Exception exp)
{
var instance = Activator.CreateInstance<T>();
instance.errorMessage = "something wrong here";
return instance;
}
;
}
public static XmlDocument doc()
{
var doc = new XmlDocument();
var el = (XmlElement)doc.AppendChild(doc.CreateElement("Result"));
el.SetAttribute("status", "ok");
el.SetAttribute("status2", "notok");
return doc;
}
}
interface IResult
{
string status { get; set; }
string errorMessage { get; set; }
}
[Serializable]
public class Result : IResult
{
[XmlAttribute]
public string status { get; set; }
[XmlAttribute]
public string errorMessage { get; set; }
[XmlAttribute]
public string message { get; set; }
}
[Serializable]
public class Result2 : IResult
{
[XmlAttribute]
public string status { get; set; }
[XmlAttribute]
public string message2 { get; set; }
[XmlAttribute]
public string errorMessage { get; set; }
}
[Serializable]
public class Result3
{
[XmlAttribute]
public string status { get; set; }
[XmlAttribute]
public string message2 { get; set; }
[XmlAttribute]
public string errorMessage { get; set; }
}
}

Related

How to serialize/deserialize an ArrayList and properties that are of an Object type

how can I get the same object types when I deserialize a json object? The ArrayList is lost and deserialized to an object array and the rectangle is completely lost.
Edit: I cannot change the object types, the payload has to be of type object and I won't know what's in it. I added just 1 example of data contained within but it will vary.
public static void Run()
{
Int32 retval = 0;
ArrayList list = new ArrayList();
list.Add(retval);
list.Add(new Rectangle(1, 1, 1, 1));
Bar bar = new Bar()
{
MessageType = Bar.MessageTypes.Msg1,
Payload = list
};
Newtonsoft.Json.JsonSerializerSettings settings = new Newtonsoft.Json.JsonSerializerSettings()
{
TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto
};
var json1 = Newtonsoft.Json.JsonConvert.SerializeObject(bar);
var temp1 = Newtonsoft.Json.JsonConvert.DeserializeObject<Bar>(json1);
var json2 = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(bar);
var temp2 = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Bar>(json2);
}
public class Bar
{
public enum MessageTypes
{
Msg1 = 1,
Msg2 = 2
}
public MessageTypes MessageType { get; set; }
public Object Payload { get; set; }
}
It can't deserialize because it is not aware of the types it needs to serialize to.
Don't use an ArrayList, try to specify the properties of your payload . Something like this should work:
public enum MessageTypes
{
Msg1 = 1,
Msg2 = 2
}
public class PayLoad
{
public int Foo { get; set; }
public Rectangle Rectangle { get; set; }
}
public class Bar
{
public MessageTypes MessageType { get; set; }
public PayLoad Payload { get; set; }
}
I decided to use soap serialization instead as it adds the types into the xml
using System;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Soap;
namespace Utils
{
public class XMLUtil
{
public static Byte[] StringToUTF8ByteArray(String xmlString)
{
return new UTF8Encoding().GetBytes(xmlString);
}
public static String SerializeToXML<T>(T objectToSerialize)
{
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms, Encoding.UTF8))
{
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
soapFormatter.Serialize(ms, objectToSerialize);
String decoded = Encoding.UTF8.GetString(ms.ToArray());
return decoded;
}
}
public static T DeserializeFromXML<T>(string xmlString) where T : class
{
T retval = default(T);
using (MemoryStream stream = new MemoryStream(StringToUTF8ByteArray(xmlString)))
{
SoapFormatter soapFormatter = new SoapFormatter();
soapFormatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
retval = soapFormatter.Deserialize(stream) as T;
}
return retval;
}
}
}

casting a string from xml to class object

I want to cast a string from xml file to be casted to ContactPersonType
See for loop for where string needs to be casted
public class ContactPersonType
{
private String _id;
public String ID
{
get { return _id; }
set { _id = value; }
}
private String _name;
public String Name
{
get { return _name; }
set { _name = value; }
}
}
//ContactPerson class
private ContactPersonType _jobRole;
public ContactPersonType JobRole
{
get { return _jobRole; }
set { _jobRole = value; }
}
public static ObservableCollection<ContactPerson> getContactPerson()
{
ObservableCollection<ContactPerson> ContactPersons = new ObservableCollection<ContactPerson>();
XmlDocument doc = new XmlDocument();
doc.Load("contactpersoon.xml");
XmlNodeList elemList = doc.GetElementsByTagName("contact");
for (int i = 0; i < elemList.Count; i++)
{
//this needs to be casted to ContactPersonType
contactPerson.JobRole = elemList[i]["jobrole"].InnerText;
}
return ContactPersons;
}
I'm not really familliar with the way you read the XML elements so this code might need some tweaking but this should be along the lines of what you're looking for (also took the liberty of making your properties autoimplemented for higher code readability)
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Program
{
public class ContactPersonType
{
public string ID { get; set; }
public string Name { get; set; }
}
public class ContactPerson
{
public ContactPersonType JobRole { get; set; }
public static ObservableCollection<ContactPerson> GetContactPerson()
{
var contactPersons = new ObservableCollection<ContactPerson>();
XElement doc = XElement.Load("contactpersoon.xml");
var contacts = doc.Elements("contact");
for (int i = 0; i < contacts.Count(); i++)
{
contactPersons.Add(new ContactPerson
{
JobRole = new ContactPersonType
{
ID = i.ToString(),
Name = contacts.ElementAt(i).Element("jobrole").Value
}
});
}
return contactPersons;
}
}
}
Depending on how you set the ID property you could possibly rewrite the loop as a foreach. For more info on XElement and it's members, check out http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement(v=vs.110).aspx and http://msdn.microsoft.com/en-us/vstudio/bb688087.aspx
Can you not deserialize the XML to the .NET class with a helper such as the one shown below?
public T Deserialize<T>(string fileName)
{
try
{
XmlSerializer sx = new XmlSerializer(typeof(T)); //or T.GetType?
StreamReader sr = new StreamReader(fileName);
var data = sx.Deserialize(sr);
sr.Close();
return (T)data;
}
catch (Exception ex)
{
throw;
}
}
I hate XmlDocument, so always deserialize where possible (though a potential alternative to i do like is XDocument).
EDIT - the usage would be something like:
var item = Deserialize<ContactPersonType>(fileName);

JSON deserialize List<SomeObject> is not working

We are deserializing the JSON using C# System.Runtime.Serialization.Json.DataContractJsonSerializer. It is working for normal objects but not for List.
For example if the json string is below then it works fine:
{"CacheInsertDateTime":"\/Date(1360761324878)\/","Data":{"__type":"SomeObject:#ConsoleApplication1","Symbol":"some string"}}
But if the josn is below:
{"CacheInsertDateTime":"\/Date(1360761324878)\/","Data":[{"__type":"SomeObject:#ConsoleApplication1","Symbol":"some string"},{"__type":"SomeObject:#ConsoleApplication1","Symbol":"some other string"}]}
The data comes as List<Object> not List<SomeObject>. I attached the sample solution also, showing the same problem. Any help or direction is appreciated.
EDIT: added the code
{
namespace ConsoleApplication1
{
class Program
{
/* Someobject class*/
[DataContract]
public class SomeObject
{
public SomeObject(string sym)
{
this.Symbol = sym;
}
[DataMember]
public string Symbol { get; set; }
}
/* Cahe Data */
[DataContract()]
[KnownType("GetKnownTypes")]
class CacheData
{
[DataMember()]
public object Data { get; set; }
[DataMember()]
public DateTime CacheInsertDateTime { get; set; }
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
return GetKnownTypes();
}
public static IEnumerable<Type> GetKnownTypes()
{
if (knownTypes == null)
{
// Since reflection is costly, we will do the lookup once for the known types and persist the data in knownTypes variable
knownTypes = new List<Type>();
// first add types from DataModel assembly get types which are marked with DataContract attribute
var typesInCurrentAssembly = Assembly.GetExecutingAssembly().GetTypes().Where
(t => t.GetCustomAttributes(false).Any(attrib => attrib is DataContractAttribute));
foreach (var type in typesInCurrentAssembly)
{
// add type and list<type> also to the known types list
knownTypes.Add(type);
knownTypes.Add(typeof(List<>).MakeGenericType(type));
}
knownTypes.Add(typeof(DataTable));
}
return knownTypes;
}
private static List<Type> knownTypes = null;
}
/*Cache Response Class*/
class CacheResponse<T> where T : class
{
public CacheData CacheData { get; set; }
public T Data
{
get{return (CacheData != null && CacheData.Data.GetType() == typeof(T)) ? CacheData.Data as T: null;}
}
}
/* Main class */
static void Main(string[] args)
{
//1. first with someobject, it works same as above
string jsonString = "{\"CacheInsertDateTime\":\"\\/Date(1360761324878)\\/\",\"Data\":{\"__type\":\"SomeObject:#ConsoleApplication1\",\"Symbol\":\"some object 1\"}}";
CacheData obj = null;
byte[] byteData = Encoding.UTF8.GetBytes(jsonString);
using (MemoryStream stream = new MemoryStream(byteData))
{
var serializer = new DataContractJsonSerializer(typeof(CacheData));
obj = serializer.ReadObject(stream) as CacheData;
}
CacheResponse<SomeObject> response1 = new CacheResponse<SomeObject>();
response1.CacheData = obj;
SomeObject responseObj = response1.Data; //this response object is fine
//2. with list<someobject>, it does not work
jsonString = "{\"CacheInsertDateTime\":\"\\/Date(1360761324878)\\/\",\"Data\":[{\"__type\":\"SomeObject:#ConsoleApplication1\",\"Symbol\":\"some object 1\"},{\"__type\":\"SomeObject:#ConsoleApplication1\",\"Symbol\":\"some object 2\"}]}";
byteData = Encoding.UTF8.GetBytes(jsonString);
using (MemoryStream stream = new MemoryStream(byteData))
{
var serializer = new DataContractJsonSerializer(typeof(CacheData));
obj = serializer.ReadObject(stream) as CacheData;
}
CacheResponse<List<SomeObject>> response2 = new CacheResponse<List<SomeObject>>();
response2.CacheData = obj;
List<SomeObject> responseList = response2.Data;//this is null
}
}
}
Make sure to let the serializer know what types to expect in the list, for example by passing it to the constructor of DataContractJsonSerializer.
The following example works as expected:
namespace ConsoleApplication1
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
public class SomeObject
{
public string Symbol { get; set; }
}
public class MyClass
{
public DateTime CacheInsertTime { get; set; }
public List<object> Data { get; set; }
}
public class Program
{
private const string JsonString = #"{""CacheInsertDateTime"":""\/Date(1360761324878)\/"",""Data"":[{""__type"":""SomeObject:#ConsoleApplication1"",""Symbol"":""some string""},{""__type"":""SomeObject:#ConsoleApplication1"",""Symbol"":""some other string""}]}";
private static void Main()
{
var ser = new DataContractJsonSerializer(typeof (MyClass), new[] {typeof (SomeObject)});
var ms = new MemoryStream(Encoding.ASCII.GetBytes(JsonString));
var obj = (MyClass) ser.ReadObject(ms);
Trace.Assert(obj.Data.Count == 2);
Trace.Assert(((SomeObject) obj.Data[1]).Symbol == "some other string");
}
}
}
Note how I pass typeof(SomeType) to the constructor of the serializer.

Discover output class from json.net

I'm doing a project where I dynamically build a set of objects based on reflection and serialize it (using json.net).
What I want to do is to deserialize it and recreate the object tree, but I don't know the final class type.
So how can I find out?
Here's an example:
public class insideBleah
{
public int BProperty
{ get; set; }
}
public class bleah
{
public int AProperty
{ get; set; }
public insideBleah Inside
{ get; set; }
}
and
var bleah = new bleah();
bleah.AProperty = 1;
bleah.Inside = new insideBleah();
bleah.Inside.BProperty = 2;
var output = JsonConvert.SerializeObject(bleah, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects
});
This produces the output string:
"{\"$type\":\"jsontest.bleah, jsontest\",\"AProperty\":1,\"Inside\":{\"$type\":\"jsontest.insideBleah, jsontest\",\"BProperty\":2}}"
So I can see the class in there. It's so close! But I can't seem to tease the information out.
How can I do this? I can't do:
var newObject = (bleah)JsonConvert.DeserializeObject<bleah>(output);
because I don't know it's a 'bleah'.
How can I handle this?
Thanks in advance
Why can you just use:
using System;
using Newtonsoft.Json;
namespace ConsoleApplication1
{
class Program
{
private static void Main(string[] args)
{
var bleah = new Person();
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects };
var output = JsonConvert.SerializeObject(bleah, settings);
Console.WriteLine(output);
var deserializeObject = JsonConvert.DeserializeObject(output, settings);
Console.WriteLine(deserializeObject.GetType().Name);
}
}
class Person
{
public string Name { get; set; }
}
}
The output:
{"$type":"ConsoleApplication1.Person, ConsoleApplication1","Name":null}
Person

Deserialize array values to .NET properties using DataContractJsonSerializer

I'm working with the DataContractJsonSerializer in Silverlight 4 and would like to deserialize the following JSON:
{
"collectionname":"Books",
"collectionitems": [
["12345-67890",201,
"Book One"],
["09876-54321",45,
"Book Two"]
]
}
Into classes like the following:
class BookCollection
{
public string collectionname { get; set; }
public List<Book> collectionitems { get; set; }
}
class Book
{
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
}
What's the proper place to extend DataContractJsonSerializer to map the unnamed first array element in "collectionitems" to the Id property of the Book class, the second element to the NumberOfPages property and the final element to Title? I don't have control over the JSON generation in this instance and would like the solution to work with the Silverlight subset of .NET. It would be great if the solution could perform the reverse for serialization as well.
If this weren't Silverlight, you could use IDataContractSurrogate to use object[] (what's actually present in your JSON) instead of Book when serializing/deserializing. Sadly, IDataContractSurrogate (and the overloads of the DataContractJsonSerializer constructor which use it) aren't available in Silverlight.
On Silverlight, here's a hacky but simple workaround. Derive the Book class from a type which imlpements ICollection<object>. Since the type in your serialized JSON is object[], the framework will dutifully serialize it into your ICollection<object>, which in turn you can wrap with your properties.
The easiest (and hackiest) is just to derive from List<object>. This easy hack has the downside that users can modify the underlying list data and mess up your properties. If you're the only user of this code, that might be OK. With a little more work, you can roll your own implementation of ICollection and permit only enough methods to run for serialization to work, and throwing exceptions for the rest. I included code samples for both approaches below.
If the above hacks are too ugly for you, I'm sure there are more graceful ways to handle this. You'd probably want to focus your attention on creating a custom collection type instead of List<Book> for your collectionitems property. This type could contain a field of type List<object[]> (which is the actual type in your JSON) which you might be able to convince the serializer to populate. Then your IList implementation could mine that data into actual Book instances.
Another line of investigation could try casting.For example could you implement an implicit type conversion between Book and string[] and would serialization be smart enough to use it? I doubt it, but it may be worth a try.
Anyway, here's code samples for the derive-from-ICollection hacks noted above. Caveat: I haven't verified these on Silverlight, but they should be using only Silverlight-accessible types so I think (fingers crossed!) it should work OK.
Easy, Hackier Sample
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
[DataContract]
class BookCollection
{
[DataMember(Order=1)]
public string collectionname { get; set; }
[DataMember(Order = 2)]
public List<Book> collectionitems { get; set; }
}
[CollectionDataContract]
class Book : List<object>
{
public string Id { get { return (string)this[0]; } set { this[0] = value; } }
public int NumberOfPages { get { return (int)this[1]; } set { this[1] = value; } }
public string Title { get { return (string)this[2]; } set { this[2] = value; } }
}
class Program
{
static void Main(string[] args)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
string json = "{"
+ "\"collectionname\":\"Books\","
+ "\"collectionitems\": [ "
+ "[\"12345-67890\",201,\"Book One\"],"
+ "[\"09876-54321\",45,\"Book Two\"]"
+ "]"
+ "}";
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
BookCollection obj = ser.ReadObject(ms) as BookCollection;
using (MemoryStream ms2 = new MemoryStream())
{
ser.WriteObject(ms2, obj);
string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0, (int)ms2.Length);
}
}
}
}
Harder, slightly-less-hacky sample
Here's the second sample, showing a manual implementation of ICollection, which prevents users from accessing the collection-- it supports calling Add() 3 times (during deserialization) but otherwise doesn't allow modification via ICollection<T>. The ICollection methods are exposed using explicit interface implementation and there are attributes on those methods to hide them from intellisense, which should further reduce the hack factor. But as you can see this is a lot more code.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
[DataContract]
class BookCollection
{
[DataMember(Order=1)]
public string collectionname { get; set; }
[DataMember(Order = 2)]
public List<Book> collectionitems { get; set; }
}
[CollectionDataContract]
class Book : ICollection<object>
{
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
// code below here is only used for serialization/deserialization
// keeps track of how many properties have been initialized
[EditorBrowsable(EditorBrowsableState.Never)]
private int counter = 0;
[EditorBrowsable(EditorBrowsableState.Never)]
public void Add(object item)
{
switch (++counter)
{
case 1:
Id = (string)item;
break;
case 2:
NumberOfPages = (int)item;
break;
case 3:
Title = (string)item;
break;
default:
throw new NotSupportedException();
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerator<object> System.Collections.Generic.IEnumerable<object>.GetEnumerator()
{
return new List<object> { Id, NumberOfPages, Title }.GetEnumerator();
}
[EditorBrowsable(EditorBrowsableState.Never)]
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new object[] { Id, NumberOfPages, Title }.GetEnumerator();
}
[EditorBrowsable(EditorBrowsableState.Never)]
int System.Collections.Generic.ICollection<object>.Count
{
get { return 3; }
}
[EditorBrowsable(EditorBrowsableState.Never)]
bool System.Collections.Generic.ICollection<object>.IsReadOnly
{ get { throw new NotSupportedException(); } }
[EditorBrowsable(EditorBrowsableState.Never)]
void System.Collections.Generic.ICollection<object>.Clear()
{ throw new NotSupportedException(); }
[EditorBrowsable(EditorBrowsableState.Never)]
bool System.Collections.Generic.ICollection<object>.Contains(object item)
{ throw new NotSupportedException(); }
[EditorBrowsable(EditorBrowsableState.Never)]
void System.Collections.Generic.ICollection<object>.CopyTo(object[] array, int arrayIndex)
{ throw new NotSupportedException(); }
[EditorBrowsable(EditorBrowsableState.Never)]
bool System.Collections.Generic.ICollection<object>.Remove(object item)
{ throw new NotSupportedException(); }
}
class Program
{
static void Main(string[] args)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(BookCollection));
string json = "{"
+ "\"collectionname\":\"Books\","
+ "\"collectionitems\": [ "
+ "[\"12345-67890\",201,\"Book One\"],"
+ "[\"09876-54321\",45,\"Book Two\"]"
+ "]"
+ "}";
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
BookCollection obj = ser.ReadObject(ms) as BookCollection;
using (MemoryStream ms2 = new MemoryStream())
{
ser.WriteObject(ms2, obj);
string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0, (int)ms2.Length);
}
}
}
}
BTW, the first time I read your quesiton I skipped over the important Silverlight requirement. Oops! Anyway, if not using Silverlight, here's the solution I coded up for that case-- it's much easier and I might as well save it here for any Googlers coming later.
The (on regular .NET framework, not Silverlight) magic you're looking for is IDataContractSurrogate. Implement this interface when you want to substitute one type for another type when serializing/deserializing. In your case you wnat to substitute object[] for Book.
Here's some code showing how this works:
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;
using System.Collections.ObjectModel;
[DataContract]
class BookCollection
{
[DataMember(Order=1)]
public string collectionname { get; set; }
[DataMember(Order = 2)]
public List<Book> collectionitems { get; set; }
}
class Book
{
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
}
// A type surrogate substitutes object[] for Book when serializing/deserializing.
class BookTypeSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
// "Book" will be serialized as an object array
// This method is called during serialization, deserialization, and schema export.
if (typeof(Book).IsAssignableFrom(type))
{
return typeof(object[]);
}
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
// This method is called on serialization.
if (obj is Book)
{
Book book = (Book) obj;
return new object[] { book.Id, book.NumberOfPages, book.Title };
}
return obj;
}
public object GetDeserializedObject(object obj, Type targetType)
{
// This method is called on deserialization.
if (obj is object[])
{
object[] arr = (object[])obj;
Book book = new Book { Id = (string)arr[0], NumberOfPages = (int)arr[1], Title = (string)arr[2] };
return book;
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null; // not used
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
return typeDeclaration; // Not used
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null; // not used
}
public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null; // not used
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
return; // not used
}
}
class Program
{
static void Main(string[] args)
{
DataContractJsonSerializer ser =
new DataContractJsonSerializer(
typeof(BookCollection),
new List<Type>(), /* knownTypes */
int.MaxValue, /* maxItemsInObjectGraph */
false, /* ignoreExtensionDataObject */
new BookTypeSurrogate(), /* dataContractSurrogate */
false /* alwaysEmitTypeInformation */
);
string json = "{"
+ "\"collectionname\":\"Books\","
+ "\"collectionitems\": [ "
+ "[\"12345-67890\",201,\"Book One\"],"
+ "[\"09876-54321\",45,\"Book Two\"]"
+ "]"
+ "}";
using (MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json)))
{
BookCollection obj = ser.ReadObject(ms) as BookCollection;
using (MemoryStream ms2 = new MemoryStream())
{
ser.WriteObject(ms2, obj);
string serializedJson = Encoding.UTF8.GetString(ms2.GetBuffer(), 0, (int)ms2.Length);
}
}
}
}
I find your question very interesting. So I have to spent my time in trying to solve the problem. Currently I received an example which can serialize and deserealize JSON data like following:
{
"collectionname":"Books",
"collectionitems":[
{"book":["12345-67890",201,"Book One"]},
{"book":["09876-54321",45,"Book Two"]}
]
}
the corresponding code of a small console application:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace DataContractJsonSerializer {
[DataContract]
class BookCollection {
[DataMember (Order = 0)]
public string collectionname { get; set; }
[DataMember (Order = 1)]
public List<Book> collectionitems { get; set; }
}
[Serializable]
[KnownType (typeof (object[]))]
class Book: ISerializable {
public string Id { get; set; }
public int NumberOfPages { get; set; }
public string Title { get; set; }
public Book () { }
[SecurityPermissionAttribute (SecurityAction.Demand, Flags = SecurityPermissionFlag.SerializationFormatter)]
protected Book (SerializationInfo info, StreamingContext context) {
// called by DataContractJsonSerializer.ReadObject
Object[] ar = (Object[]) info.GetValue ("book", typeof (object[]));
this.Id = (string)ar[0];
this.NumberOfPages = (int)ar[1];
this.Title = (string)ar[2];
}
[SecurityPermission (SecurityAction.Demand, SerializationFormatter = true)]
public void GetObjectData (SerializationInfo info, StreamingContext context) {
// called by DataContractJsonSerializer.WriteObject
object[] ar = new object[] { (object)this.Id, (object)this.NumberOfPages, (object)this.Title };
info.AddValue ("book", ar);
}
}
class Program {
static readonly string testJSONdata = "{\"collectionname\":\"Books\",\"collectionitems\":[{\"book\":[\"12345-67890\",201,\"Book One\"]},{\"book\":[\"09876-54321\",45,\"Book Two\"]}]}";
static void Main (string[] args) {
BookCollection test = new BookCollection () {
collectionname = "Books",
collectionitems = new List<Book> {
new Book() { Id = "12345-67890", NumberOfPages = 201, Title = "Book One"},
new Book() { Id = "09876-54321", NumberOfPages = 45, Title = "Book Two"},
}
};
MemoryStream memoryStream = new MemoryStream ();
System.Runtime.Serialization.Json.DataContractJsonSerializer ser =
new System.Runtime.Serialization.Json.DataContractJsonSerializer (typeof (BookCollection));
memoryStream.Position = 0;
ser.WriteObject (memoryStream, test);
memoryStream.Flush();
memoryStream.Position = 0;
StreamReader sr = new StreamReader(memoryStream);
string str = sr.ReadToEnd ();
Console.WriteLine ("The result of custom serialization:");
Console.WriteLine (str);
if (String.Compare (testJSONdata, str, StringComparison.Ordinal) != 0) {
Console.WriteLine ("Error in serialization: unexpected results.");
return;
}
byte[] jsonDataAsBytes = System.Text.Encoding.GetEncoding ("iso-8859-1").GetBytes (testJSONdata);
MemoryStream stream = new MemoryStream (jsonDataAsBytes);
stream.Position = 0;
BookCollection p2 = (BookCollection)ser.ReadObject (stream);
}
}
}
I don't yet tested this approach under Silverlight 4.

Categories

Resources