Serializing anonymous types - c#

I'd like to convert anonymous type variable to byte[], how can I do that?
What I tried:
byte[] result;
var my = new
{
Test = "a1",
Value = 0
};
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, my); //-- ERROR
result = ms.ToArray();
}
I've got error:
An exception of type
'System.Runtime.Serialization.SerializationException' occurred in
mscorlib.dll but was not handled in user code
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'
in Assembly 'MyProject, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null' is not marked as serializable.Additional
information: Type '<>f__AnonymousType10`2[[System.String, mscorlib,
Can somebody help me? What I'm doing wrong? Or this is not possible to do?

Just create a serializable class
[Serializable]
class myClass
{
public string Test { get; set; }
public int Value { get; set; }
}
And you can serialize your object the following way:
byte[] result;
myClass my = new myClass()
{
Test = "a1",
Value = 0
};
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, my); //NO MORE ERROR
result = ms.ToArray();
}
But i'ts not possible to serialize a anonymous type

Types that are to be serialized with the default serializer family (XmlSerializer, BinaryFormatter, DataContractSerializer, ...) need to be marked [Serializable], need to be public types, and will require public read-write properties.
Anonymous types don't fulfill this role, because they have none of the required properties.
Just create a type that does, and serialize that instead.

Firstly: the correct approach is, as others have pointed out, to create a proper class for serialization.
However, it is actually possible to serialize an anonymous object using Json.Net. Note that I do not recommend actually doing this in a real project - it's a curiosity only.
This code relies on a sneaky way of accessing the underlying type of an anonymous object by using an exemplar object as a type holder:
using System;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
public class Program
{
static void Main()
{
var data = serializeAnonymousObject();
deserializeAnonymousObject(data);
}
static byte[] serializeAnonymousObject()
{
// This is in a separate method to demonstrate that you can
// serialize in one place and deserialize in another.
var my = new
{
Test = "a1",
Value = 12345
};
return Serialize(my);
}
static void deserializeAnonymousObject(byte[] data)
{
// This is in a separate method to demonstrate that you can
// serialize in one place and deserialize in another.
var deserialized = new // Used as a type holder
{
Test = "",
Value = 0
};
deserialized = Deserialize(deserialized, data);
Console.WriteLine(deserialized.Test);
Console.WriteLine(deserialized.Value);
}
public static byte[] Serialize(object obj)
{
using (var ms = new MemoryStream())
using (var writer = new BsonWriter(ms))
{
new JsonSerializer().Serialize(writer, obj);
return ms.ToArray();
}
}
public static T Deserialize<T>(T typeHolder, byte[] data)
{
using (var ms = new MemoryStream(data))
using (var reader = new BsonReader(ms))
{
return new JsonSerializer().Deserialize<T>(reader);
}
}
}

You can but only if you are insane. Don't use this. This was more a fun problem.
class Program
{
static void Main(string[] args)
{
var obj1 = new
{
Test = "a1",
SubObject = new
{
Id = 1
},
SubArray = new[] { new { Id = 1 }, new { Id = 2 } },
Value = 0
};
var my = new AnonymousSerializer(obj1);
BinaryFormatter bf = new BinaryFormatter();
byte[] data;
using (MemoryStream ms = new MemoryStream())
{
bf.Serialize(ms, my);
ms.Close();
data = ms.ToArray();
}
using (MemoryStream ms = new MemoryStream(data))
{
var a = bf.Deserialize(ms) as AnonymousSerializer;
var obj2 = a.GetValue(obj1);
Console.WriteLine(obj1 == obj2);
}
Console.ReadLine();
}
[Serializable]
public class AnonymousSerializer : ISerializable
{
private object[] properties;
public AnonymousSerializer(object objectToSerializer)
{
Type type = objectToSerializer.GetType();
properties = type.GetProperties().Select(p =>
{
if (p.PropertyType.IsArray && IsAnonymousType(p.PropertyType.GetElementType()))
{
var value = p.GetValue(objectToSerializer) as IEnumerable;
return value.Cast<object>().Select(obj => new AnonymousSerializer(obj)).ToArray() ;
}else if (IsAnonymousType(p.PropertyType))
{
var value = p.GetValue(objectToSerializer);
return new AnonymousSerializer(value);
}else{
return p.GetValue(objectToSerializer);
}
}).ToArray();
}
public AnonymousSerializer(SerializationInfo info, StreamingContext context)
{
properties = info.GetValue("properties", typeof(object[])) as object[];
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("properties", properties);
}
public T GetValue<T>(T prototype)
{
return GetValue(typeof(T));
}
public dynamic GetValue(Type type)
{
Expression<Func<object>> exp = Expression.Lambda<Func<object>>(Creator(type));
return exp.Compile()();
}
private Expression Creator(Type type)
{
List<Expression> param = new List<Expression>();
for (int i = 0; i < type.GetConstructors().First().GetParameters().Length; i++)
{
var cParam = type.GetConstructors().First().GetParameters()[i];
if (cParam.ParameterType.IsArray && IsAnonymousType(cParam.ParameterType.GetElementType()))
{
var items = properties[i] as AnonymousSerializer[];
var itemType = cParam.ParameterType.GetElementType();
var data = items.Select(aser => aser.Creator(itemType)).ToArray();
param.Add(Expression.NewArrayInit(itemType, data));
}
else if (IsAnonymousType(cParam.ParameterType))
{
param.Add((properties[i] as AnonymousSerializer).Creator(cParam.ParameterType));
}
else
{
param.Add(Expression.Constant(properties[i]));
}
}
return Expression.New(type.GetConstructors().First(), param);
}
private static bool IsAnonymousType(Type type)
{
bool hasCompilerGeneratedAttribute = type.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Count() > 0;
bool nameContainsAnonymousType = type.FullName.Contains("AnonymousType");
bool isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType;
return isAnonymousType;
}
}
}
}

Similar to previous answers, I am using JSON.NET hack;
public static byte[] DynamicToByteArray(object message)
{
string serializeObject = JsonConvert.SerializeObject(message);
byte[] bytes = Encoding.UTF8.GetBytes(serializeObject);
return bytes;
}
I am using dynamic objects my logging purposes, and it works very well since I do not need schema for that.

Related

Setting an additional caller-defined context for DataContractJsonSerializer

For BinaryFormatter I can set an additional caller-defined context, which will be passed as parameter to the method called after deserialization, e.g. decorated with [OnDeserialized] attribute.
Calling the method decorated with [OnDeserialized] for DataContractJsonSerializer works, but how can I set an additional caller-defined context for it? Is it possible?
I would like to avoid to call some dedicated method like SetBusinessObj(..) manually after deserialization.
var businessObject = "some business object";
var serObj = new SerialClass(businessObject) { Data1 = "some data1" };
using(var ms = new MemoryStream())
{
var bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Other, businessObject));
bf.Serialize(ms, serObj);
ms.Flush();
ms.Position = 0;
var deserObj = bf.Deserialize(ms);
}
using(var ms = new MemoryStream())
{
//How to set an additional caller-defined context?
var jsnSer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(SerialClass));
jsnSer.WriteObject(ms, serObj);
ms.Flush();
ms.Position = 0;
var deserObj = jsnSer.ReadObject(ms);
}
[Serializable]
class SerialClass
{
public string Data1 { get; set; }
private object _businessObj = null;
public SerialClass(object bo)
{
_businessObj = bo;
}
[OnDeserialized]
private void OnDeserialized(StreamingContext sctx)
{
if(sctx.Context is string dataIsHere)
{
_businessObj = dataIsHere;
}
}
}

A constructor that takes object from same type

So I want to create constructor for my class EmployeeNodeClass that takes In EmployeeNodeClass object and copies it using a deepclone funtion:
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
to the new object.
At first i thought that it is as simple as
public EmployeeNodeClass(EmployeeNodeClass EMPND)
{
this = DeepClone(EMPND);
}
but then I got the error that this is readonly.
so how can I do it?
It can be done using NewtonSoft JsonConvert.PopulateObject:
Example:
class Entity
{
public List<int> List { get; set; }
public Entity(List<int> list)
{
List = list;
}
public Entity(Entity original)
{
string originalJson = JsonConvert.SerializeObject(original);
JsonConvert.PopulateObject(originalJson, this);
}
}
And using it:
static void Main(string[] args)
{
var entity1 = new Entity(new List<int> { 1, 2, 3 });
var entity2 = new Entity(entity1);
entity1.List[0] = 5;
Console.WriteLine(entity2.List[0]);
}
Note: since this uses NewtonSoft Json, it will only clone public, writable properties. So internal state in private fields (that are not associated with such properties) will be lost.

XmlNodeConverter only supports deserializing XmlDocuments error when using Json DeserializeObject

I've the following class;
public class MyClass
{
[XmlIgnore]
public string Name { get; set; }
[XmlElement("Name")]
public XmlCDataSection sName
{
get { return new XmlDocument().CreateCDataSection(Name); }
set { Name = value.Value; }
}
}
I've the following function to take a List<> and copy it's contents;
private static T CloneList<T>(T source)
{
var serialized = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(serialized);
}
But in my code when I try;
List<MyClass> oMyClassList = new List<MyClass>();
MyClass oMyClass = new MyClass();
oMyClass.Name = "Hello World's";
oMyClassList.Add(oMyClass);
List<MyClass> oMyClonedClassList = new List<MyClass>(CloneList(oMyClassList));
At the point of executing the following
List<MyClass> oMyClonedClassList = new List<MyClass>(CloneList(oMyClassList));
I get the error XmlNodeConverter only supports deserializing XmlDocuments. The problem occurs because I've added XmlCDataSection into the class.
How can I get around this problem ?
I managed to overcome this problem by changing my CloneList code to the following
public static T DeepClone<T>(T obj)
{
T objResult;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = (T)bf.Deserialize(ms);
}
return objResult;
}
As provided by [Ajith][1] here How do I clone a generic list in C#?
[1]: https://stackoverflow.com/users/853645/ajith "ajith".
Also, to each of the classes that need to be cloned, I had to add [Serializable] at the top of each class as I was getting the exception 'Not marked as serializable'

Serializing Arrays in C#

Is there a way to Serialize an entire array in C# such as:
[Serializable()]
public class Data
{
public short Some_Short_Data = new short[100,100];
public string Some_String_Data = new string[100,100];
}
Then writting the file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
Data Write_Data = new Data();
XmlSerializer Writer = new XmlSerializer(typeof(Data));
using (FileStream file = File.OpenWrite("Myfile.xml"))
{
Writer.Serialize(file, Write_Data); //Writes data to the file
}
When I try this, it fails on:
XmlSerializer Writer = new XmlSerializer(typeof(Data));
Saying: "There was an error reflecting type 'Data' "
I am particularly new to both Stack and Xml/Xml Serialization so any help would be appreciated.
Multi-dimensional arrays are not supported by XmlSerialzier. But you can make a workaround by using a temp class something like this
public class Array100<T>
{
public T[] Data = new T[100];
}
public class Data
{
public Array100<short>[] Some_Short_Data = new Array100<short>[100];
public Array100<string>[] Some_String_Data = new Array100<string>[100];
}
BTW: No need for Serializable attribute. It is not used by XmlSerialzier
You cannot serialze a int[,] but you can serialize a int[][]. Before serializing your 2D array, just convert it into a jagged array like that :
var my2dArray = new int[2,5];
var myJaggedArray = new int [2][];
for(int i = 0 ; i < my2DArray.GetLength(0) ; i ++)
{
myJaggedArray[i] = new int[my2DArray.GetLength(1)];
for(int j = 0 ; j < my2DArray.GetLength(1) ; j ++)
myJaggedArray[i][j] = my2DArray[i,j];
}
I ran into this issue when attempting to serialize a multi-dimensional array that was one of the values in a dictionary of objects. The approach I took was to wrap all of the arrays in a class that is serializable before serialization and then unwrap them all afterwards.
If you don't care what the data looks like then you can serialize the object using the binary formatter, which can serialize anything marked as serializable, and then persist the binary data as a base 64 encoded string.
The wrapper implementation is below.
[Serializable]
public class UnserializableValueWrapper: IXmlSerializable
{
private static readonly BinaryFormatter Formatter = new BinaryFormatter();
public UnserializableValueWrapper([NotNull] object value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.Value = value;
}
public UnserializableValueWrapper()
{
}
public object Value { get; private set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement();
var length = int.Parse(reader.GetAttribute("length"));
reader.ReadStartElement("Data");
var buffer = new byte[length];
reader.ReadContentAsBase64(buffer, 0, length);
using (var stream = new MemoryStream(buffer))
{
this.Value = Formatter.Deserialize(stream);
}
reader.ReadEndElement();
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Data");
using (var stream = new MemoryStream())
{
Formatter.Serialize(stream, this.Value);
var buffer = stream.ToArray();
writer.WriteAttributeString("length", buffer.Length.ToString());
writer.WriteBase64(buffer, 0, buffer.Length);
}
writer.WriteEndElement();
}
}

Clone Whole Object Graph

While using this code to serialize an object:
public object Clone()
{
var serializer = new DataContractSerializer(GetType());
using (var ms = new System.IO.MemoryStream())
{
serializer.WriteObject(ms, this);
ms.Position = 0;
return serializer.ReadObject(ms);
}
}
I have noticed that it doesn't copy the relationships.
Is there any way to make this happen?
Simply use the constructor overload that accepts preserveObjectReferences, and set it to true:
using System;
using System.Runtime.Serialization;
static class Program
{
public static T Clone<T>(T obj) where T : class
{
var serializer = new DataContractSerializer(typeof(T), null, int.MaxValue, false, true, null);
using (var ms = new System.IO.MemoryStream())
{
serializer.WriteObject(ms, obj);
ms.Position = 0;
return (T)serializer.ReadObject(ms);
}
}
static void Main()
{
Foo foo = new Foo();
Bar bar = new Bar();
foo.Bar = bar;
bar.Foo = foo; // nice cyclic graph
Foo clone = Clone(foo);
Console.WriteLine(foo != clone); //true - new object
Console.WriteLine(clone.Bar.Foo == clone); // true; copied graph
}
}
[DataContract]
class Foo
{
[DataMember]
public Bar Bar { get; set; }
}
[DataContract]
class Bar
{
[DataMember]
public Foo Foo { get; set; }
}
Either annotate your classes with [DataContract] or add your child types in the constructor of DatacontractSerializer.
var knownTypes = new List<Type> {typeof(Class1), typeof(Class2), ..etc..};
var serializer = new DataContractSerializer(GetType(), knownTypes);
To perform a deep clone you might consider using a binary serializer:
public static object CloneObject(object obj)
{
using (var memStream = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter(
null,
new StreamingContext(StreamingContextStates.Clone));
binaryFormatter.Serialize(memStream, obj);
memStream.Seek(0, SeekOrigin.Begin);
return binaryFormatter.Deserialize(memStream);
}
}
You need a binary serializer to preserve objects identity during the serialization/deserialization step.

Categories

Resources