How to Serialize Object to Xml - c#

The class I want to store:
[Serializable]
public class Storagee
{
int tabCount;
List<string> tabNames;
List<EachItemListHolder> eachItemsHolder;
public void PreSetting(int count, List<string> strings, List<EachItemListHolder> items)
{
tabCount = count;
tabNames = strings;
eachItemsHolder = items;
}
public void PreSetting(int count ) //debug purpose
{
tabCount = count;
}
public int GetTabCount() { return tabCount; }
public List<string> GetTabNames() { return tabNames; }
public List<EachItemListHolder> GetListEachItemListHolder() { return eachItemsHolder; }
}
Serializing class:
namespace Book
{
class SaveAndLoad
{
public void SaveAll(Storagee str)
{
var path = #"C:\Temp\myserializationtest.xml";
using (FileStream fs = new FileStream(path, FileMode.Create))
{
XmlSerializer xSer = new XmlSerializer(typeof(Storagee));
xSer.Serialize(fs, str);
}
}
public Storagee LoadAll()
{
var path = #"C:\Temp\myserializationtest.xml";
using (FileStream fs = new FileStream(path, FileMode.Open)) //double
{
XmlSerializer _xSer = new XmlSerializer(typeof(Storagee));
var myObject = _xSer.Deserialize(fs);
return (Storagee)myObject;
}
}
}
}
Main method (Window form):
class Book
{
List<EachTab> eachTabs;
Storagee storagee;
SaveAndLoad saveAndLoad;
eachTabs = new List<EachTab>();
storagee = new Storagee();
saveAndLoad = new SaveAndLoad();
void Saving()
{
int count = UserTab.TabCount; // tab counts
storagee.PreSetting(count);
saveAndLoad.SaveAll(storagee);
}
}
It makes xml file but doesn't save data.
I tried the serializing code in different project and it worked.
but it doesn't in this solution
since I'm kind of new to coding I don't know what the problem is
especially serializing part.
serializing codes are copied and pasted with little tweak

It makes xml file but doesn't save data.
It doesn't save any data because your class does not provide any data that it can serialize. XmlSerializer only serializes public fields and properties and the Storagee class doesn't have any.
You could, for example, change your public getter methods to public properties:
public int TabCount { get; set; }
public List<string> TabNames { get; set; }
public List<string> EachItemsHolder { get; set; }
Alternatively, if using public properties is not an option, you could also look into using custom serialization by implementing IXmlSerializable.

Related

De-/serialization with a list of objects

I try to save and read multiple objects in one XML-File.
The function Serialize is not working with my existing List, but i dont know why. I already tried to compile it but i get an error wich says, that the methode needs an object refference.
Program.cs:
class Program
{
static void Main(string[] args)
{
List<Cocktail> lstCocktails = new List<Cocktail>();
listCocktails.AddRange(new Cocktail[]
{
new Cocktail(1,"Test",true,true,
new Cocktail(1, "Test4", true, true, 0)
});
Serialize(lstCocktails);
}
public void Serialize(List<Cocktail> list)
{
XmlSerializer serializer = new XmlSerializer(typeof(List<Cocktail>));
using (TextWriter writer = new StreamWriter(#"C:\Users\user\Desktop\MapSample\bin\Debug\ListCocktail.xml"))
{
serializer.Serialize(writer, list);
}
}
private void DiserializeFunc()
{
var myDeserializer = new XmlSerializer(typeof(List<Cocktail>));
using (var myFileStream = new FileStream(#"C:\Users\user\Desktop\MapSample\bin\Debug\ListCocktail.xml", FileMode.Open))
{
ListCocktails = (List<Cocktail>)myDeserializer.Deserialize(myFileStream);
}
}
Cocktail.cs:
[Serializable()]
[XmlRoot("locations")]
public class Cocktail
{
[XmlElement("id")]
public int CocktailID { get; set; }
[XmlElement("name")]
public string CocktailName { get; set; }
[XmlElement("alc")]
public bool alcohol { get; set; }
[XmlElement("visible")]
public bool is_visible { get; set; }
[XmlElement("counter")]
public int counter { get; set; }
private XmlSerializer ser;
public Cocktail() {
ser = new XmlSerializer(this.GetType());
}
public Cocktail(int id, string name, bool alc,bool vis,int count)
{
this.CocktailID = id;
this.CocktailName = name;
this.alcohol = alc;
this.is_visible = vis;
this.counter = count;
}
}
}
Ii also think I messed something up with the DiserializeFunc().
You are very close to implementing the Cocktail class correctly, but I think you're confused about how to serialize Lists. Your implementation of a Cocktail object class is completely fine, just get rid of the list related functions.
using System;
using System.Xml.Serialization;
namespace Serialization_Help
{
[Serializable()]
[XmlRoot("locations")]
public class Cocktail
{
[XmlElement("id")]
public int CocktailID { get; set; }
[XmlElement("name")]
public string CocktailName { get; set; }
[XmlElement("alc")]
public bool alcohol { get; set; }
[XmlElement("visible")]
public bool is_visible { get; set; }
[XmlElement("counter")]
public int counter { get; set; }
public Cocktail() {
}
public Cocktail(int id, string name, bool alc, bool vis, int count)
{
this.CocktailID = id;
this.CocktailName = name;
this.alcohol = alc;
this.is_visible = vis;
this.counter = count;
}
}
}
Now in your new function you want to serialize the list directly.
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace Serialization_Help
{
class Program {
static void Main(string[] args) {
List<Cocktail> list = new List<Cocktail> {
new Cocktail(01, "rum and coke", true, true, 5),
new Cocktail(02, "water on the rocks", false, true, 3)
};
Serialize(list);
List<Cocktail> deserialized = DiserializeFunc();
}
public static void Serialize(List<Cocktail> list) {
XmlSerializer serializer = new XmlSerializer(typeof(List<Cocktail>));
using (TextWriter writer = new StreamWriter(Directory.GetCurrentDirectory() + #"\ListCocktail.xml")) serializer.Serialize(writer, list);
}
private static List<Cocktail> DiserializeFunc() {
var myDeserializer = new XmlSerializer(typeof(List<Cocktail>));
using (var myFileStream = new FileStream(Directory.GetCurrentDirectory() + #"\ListCocktail.xml", FileMode.Open)) return (List<Cocktail>)myDeserializer.Deserialize(myFileStream);
}
}
}
Doing so should correctly print out the following .xml output:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCocktail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Cocktail>
<id>1</id>
<name>rum and coke</name>
<alc>true</alc>
<visible>true</visible>
<counter>5</counter>
</Cocktail>
<Cocktail>
<id>2</id>
<name>water on the rocks</name>
<alc>false</alc>
<visible>true</visible>
<counter>3</counter>
</Cocktail>
</ArrayOfCocktail>
Keep in mind that I have not provided implementation of any of the standard safety or null checks for the file. You'll have to check if the file exists yourself by using File.Exists(...) (see here for File.Exists implementation) and implement the correct try and catch cases and what your code will chose to do if it runs into serialization or input/outut errors.
You'd better use ExtendedXmlSerializer to serialize and deserialize.
Instalation
You can install ExtendedXmlSerializer from nuget or run the following command:
Install-Package ExtendedXmlSerializer
Serialization:
ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var list = new List<Cocktail>();
var xml = serializer.Serialize(list);
Deserialization
var list = serializer.Deserialize<List<Cocktail>>(xml);
Standard XML Serializer in .NET is very limited.
Does not support serialization of class with circular reference or class with interface property,
Does not support Dictionaries,
There is no mechanism for reading the old version of XML,
If you want create custom serializer, your class must inherit from IXmlSerializable. This means that your class will not be a POCO class,
Does not support IoC.
ExtendedXmlSerializer can do this and much more.
ExtendedXmlSerializer support .NET 4.5 or higher and .NET Core. You can integrate it with WebApi and AspCore.

Exclude (ignore) object during serialization

Have a ordinary hierarchy of objects that inherit common to all interface or abstract class.
public abstract class AActivation
{
public bool IsActivated
{ get; set; }
}
public class Root : AActivation
{
public int Value
{ get; set; }
public List<LevelOne> Childs
{ get; private set; }
public Root()
{
Value = 1000;
Childs = new List<LevelOne>();
}
}
public class LevelOne : AActivation
{
static int _i = 0;
public int Value
{ get; set; }
public List<LevelTwo> Childs
{ get; private set; }
public LevelOne()
{
Value = (++_i);
Childs = new List<LevelTwo>();
}
}
public class LevelTwo : AActivation
{
static int _i = 100;
public int Value
{ get; set; }
public LevelTwo()
{
Value = (++_i);
}
}
Objective is serialize the given object tree in XML-file excluded from serialization objects whose property IsActivated equal false.
public class Serializer
{
public static void Serialize<T>(T serializableObject , string path)
where T: AActivation , new()
{
using(StreamWriter sw = new StreamWriter(path))
{
XmlSerializer writer = new XmlSerializer(typeof(T));
writer.Serialize(sw , serializableObject);
sw.Flush();
sw.Close();
}
}
}
class Program
{
static void Main()
{
var random = new Random();
Root r = new Root();
for(int i = 0 ; i < 10 ; i++)
{
var levelOne = new LevelOne() { IsActivated = random.Next(2) > 0};
r.Childs.Add(levelOne);
for(int j = 0 ; j < 5 ; j++)
{
var levelTwo = new LevelTwo() { IsActivated = random.Next(2) > 0};
levelOne.Childs.Add(levelTwo);
}
}
string path = Path.Combine(Environment.CurrentDirectory , Path.GetRandomFileName() + ".xml");
Serializer.Serialize(r , path);
Console.ReadKey();
}
}
Now I see several solutions:
Inherit for all classes IXmlSerializable interface. But I don't want to interfere in the process of serialization as a serious way. And I need to change only the writing behavior, but also have to restore the reading behavior. After all, have to restore the default behavior, only one line of code.
if(obj.IsActivated == false) return;
Create your own XmlWriter. This lower level of XML model and again I don't want to interfere in its construction.
I hope there is a simple solution to my problem, that will not be so much to intervene in the process of serialization.

Derived Class Deserialization

I have a problem with deserialization with my logic simulation program.
Here are my element classes:
public class AndGateData : TwoInputGateData
{
}
public class TwoInputGateData : GateData
{
public TwoInputGateData()
{
Input2 = new InputData();
Input1 = new InputData();
}
public InputData Input1 { get; set; }
public InputData Input2 { get; set; }
}
public class GateData : ElementData
{
public GateData()
{
OutputData = new OutputData();
}
public OutputData OutputData { get; set; }
}
public class ElementData
{
public int Delay { get; set; }
public Guid Id { get; set; }
}
And here are classes responsible for sockets:
public class InputData : SocketData
{
}
public class SocketData
{
public Guid Id { get; set; }
public SignalData SignalData { get; set; }
}
SignalData is not important here. So, I won't write it (in order to keep this question clean) here unless somebody says it is necessary.
CircuitData is very important:
[XmlRoot("Circuit")]
public class CircuitData
{
[XmlElement(typeof(AndGateData))]
[XmlElement(typeof(OrGateData))]
public List<ElementData> elements = new List<ElementData>();
public List<WireData> wires = new List<WireData>();
public void AddElement(ElementData element)
{
elements.Add(element);
}
public void AddWire(WireData wire)
{
wires.Add(wire);
}
}
Wires are not important right now.
Now, I have written some Serialization:
public class CircuitDataWriter
{
public static void Write(object obj, string fileName)
{
var xmlFormat = new XmlSerializer(typeof(CircuitData));
using(Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None) )
{
xmlFormat.Serialize(fStream,obj);
}
Console.WriteLine("Circuit saved in XML format.");
}
}
It works just like I wanted, it produces that xml document:
<?xml version="1.0"?>
-<Circuit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-<AndGateData>
<Delay>10</Delay>
<Id>bfee6dd7-5946-4b7b-9d0b-15d5cf60e2bf</Id>
-<OutputData> <Id>00000000-0000-0000-0000-000000000000</Id> </OutputData>
-<Input1> <Id>7c767caf-79a9-4c94-9e39-5c38ec946d1a</Id> <SignalData xsi:type="SignalDataOn"/> </Input1>
-<Input2> <Id>d2cad8f8-8528-4db3-9534-9baadb6a2a14</Id> <SignalData xsi:type="SignalDataOff"/> </Input2>
</AndGateData>
<wires/>
</Circuit>
But I have problem with my DESERIALIZATION. Here is the code:
public static CircuitData Read()
{
var reader = new XmlSerializer(typeof(CircuitData));
StreamReader file = new StreamReader("Circuit.xml");
var returnCircuitData = new CircuitData();
returnCircuitData = (CircuitData) reader.Deserialize(file);
return returnCircuitData;
}
Now, it deserializes my Circuit.xml to object, but this object only contains Id and Delay, it does not contain Input1, Input2 or Output. So, it is treated like Element, not like AndGate. I tried to solve it out for a day but it seems that no one has that kind of problem.
I have a suggestion for you, make the Write method generic like this and create the serializer using objectToSerialize.GetType():
public static void Write<T>(T objectToSerialize, string fileName)
{
var xmlSerializer = new XmlSerializer(objectToSerialize.GetType());
...
}
The XmlSerializer.Deserialize() method returns object, you can make your Read method generic like this:
public static T Read<T>(string fileName)
{
var serializer = new XmlSerializer(typeof(T));
using (StreamReader file = new StreamReader(fileName))
{
return (T)serializer.Deserialize(file);
}
}
Other than that you might want to read about:
XmlInclude that is used when you serialize derived classes.
XmlArray and XmlArrayItem that are used for controlling serialization of arrays

Problem with serializing a dictionary wrapper

I defined two classes. First one...
[Serializable]
public class LocalizationEntry
{
public LocalizationEntry()
{
this.CatalogName = string.Empty;
this.Identifier = string.Empty;
this.Translation = new Dictionary<string, string>();
this.TranslationsList = new List<Translation>();
}
public string CatalogName
{
get;
set;
}
public string Identifier
{
get;
set;
}
[XmlIgnore]
public Dictionary<string, string> Translation
{
get;
set;
}
[XmlArray(ElementName = "Translations")]
public List<Translation> TranslationsList
{
get
{
var list = new List<Translation>();
foreach (var item in this.Translation)
{
list.Add(new Translation(item.Key, item.Value));
}
return list;
}
set
{
foreach (var item in value)
{
this.Translation.Add(item.Language, item.Text);
}
}
}
}
...where public List<Translation> TranslationsList is a wrapper for non-serializable public Dictionary<string, string> Translation.
Pair of key and value is defined as follows:
[Serializable]
public class Translation
{
[XmlAttribute(AttributeName = "lang")]
public string Language
{
get;
set;
}
[XmlText]
public string Text
{
get;
set;
}
public Translation()
{
}
public Translation(string language, string translation)
{
this.Language = language;
this.Text = translation;
}
}
At last code used to serialize:
static void Main(string[] args)
{
LocalizationEntry entry = new LocalizationEntry()
{
CatalogName = "Catalog",
Identifier = "Id",
};
entry.Translation.Add("PL", "jabłko");
entry.Translation.Add("EN", "apple");
entry.Translation.Add("DE", "apfel");
using (FileStream stream = File.Open(#"C:\entry.xml", FileMode.Create))
{
XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
serializer.Serialize(stream, entry);
}
LocalizationEntry deserializedEntry;
using (FileStream stream = File.Open(#"C:\entry.xml", FileMode.Open))
{
XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
deserializedEntry = (LocalizationEntry)serializer.Deserialize(stream);
}
}
The problem is that after deserialization deserializedEntry.TranslationsList is empty. I set a breakpoint at setter of LocalizationEntry.TransalionsList and it comes from deserializer empty as well. Product of serialization is of course valid. Is there any gap in my code?
EDIT:
Here is generated XML:
<?xml version="1.0"?>
<LocalizationEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CatalogName>Catalog</CatalogName>
<Identifier>Id</Identifier>
<Translations>
<Translation lang="PL">jabłko</Translation>
<Translation lang="EN">apple</Translation>
<Translation lang="DE">apfel</Translation>
</Translations>
</LocalizationEntry>
The problem is that your TranslationList property is not being set by the Xml Deserializer. The set method will be hit but only by the call to this.TranslationsList = new List(); in the LocalisationEntry constructor. I'm not yet sure why but I suspect it's because it doesn't know how to convert an array of Translation objects back into a List.
I added the following code and it worked fine:
[XmlArray(ElementName = "Translations")]
public Translation[] TranslationArray
{
get
{
return TranslationsList.ToArray();
}
set
{
TranslationsList = new List<Translation>(value);
}
}
[XmlIgnore]
public List<Translation> TranslationsList
....
I am guessing the problem has to do with this:
public List<Translation> TranslationsList
The get/set operators are designed only for something to get or assign a fully-formed list. If you tried to use this in your own code, for example, every time you would do something like
TranslationsList.Add(item)
It would just create a new list from the existing dictionary and not actually deal with your item. I bet the deserializer works much the same way: uses set to create the new object once, then uses get as it adds each item from the XML. Since all that happens in get is it copies from the dictionary (which is empty when you begin your deserialization) you end up with nothing.
Try replacing this with just a field:
public List<Translation> TranslationsList;
and then explicitly call the code to copy the dictionary to this list before you serialize, and copy it from this list to the dictionary after you deserialize. Assuming that works, you can probably figure out a more seamless way to implement what you're trying to do.
I've created a sample, which will allow you to avoid the unnecessary hidden property when using the XmlSerializer:
class Program
{
static void Main(string[] args)
{
LocalizationEntry entry = new LocalizationEntry()
{
CatalogName = "Catalog",
Identifier = "Id",
Translations =
{
{ "PL", "jabłko" },
{ "EN", "apple" },
{ "DE", "apfel" }
}
};
using (MemoryStream stream = new MemoryStream())
{
XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
serializer.Serialize(stream, entry);
stream.Seek(0, SeekOrigin.Begin);
LocalizationEntry deserializedEntry = (LocalizationEntry)serializer.Deserialize(stream);
serializer.Serialize(Console.Out, deserializedEntry);
}
}
}
public class LocalizationEntry
{
public LocalizationEntry() { this.Translations = new TranslationCollection(); }
public string CatalogName { get; set; }
public string Identifier { get; set; }
[XmlArrayItem]
public TranslationCollection Translations { get; private set; }
}
public class TranslationCollection
: Collection<Translation>
{
public TranslationCollection(params Translation[] items)
{
if (null != items)
{
foreach (Translation item in items)
{
this.Add(item);
}
}
}
public void Add(string language, string text)
{
this.Add(new Translation
{
Language = language,
Text = text
});
}
}
public class Translation
{
[XmlAttribute(AttributeName = "lang")]
public string Language { get; set; }
[XmlText]
public string Text { get; set; }
}
There are some drawbacks when working with the XmlSerializer class itself. The .NET guidelines encourage you the not provide public-setters for collection-properties (like your translation list). But when you look at the code generated by the XmlSerializer, you'll see that it will use the Setter regardless of it is accessible. This results in a compile-error when the interim class is dynamically loaded by the XmlSerializer. The only way to avoid this, is to make the XmlSerializer think, that it can't actually create an instance of the list and thus won't try to call set for it. If the XmlSerializer detects that it can't create an instance it will throw an exception instead of using the Setter and the interim class is compiled successfully. I've used the param-keyword to trick the serializer into thinking that there is no default-constructor.
The only drawback from this solution is that you have to use a non-generic, non-interface type for the property (TranslationCollection) in my example.

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