How do I deserialize into an existing object - C# - c#

In C#, after serializing an object to a file how would I deserialize the file back into an existing object without creating a new object?
All the examples I can find for custom serialization involve implementing a constructor that will be called upon deserialization which is exactly what I want except that I don't want the function to be a constructor.
Thanks!

Some serializers support callbacks; for example, both BinaryFormatter and DataContractSerializer (and protobuf-net, below) allow you to specify a before-serializaton callback, and since they skip the constructor, this may well be enough to initialize the object. The serializer is still creating it, though.
Most serializers are fussy about wanting to create the new object themselves, however some will allow you to deserialize into an existing object. Well, actually the only one that leaps to mind is protobuf-net (disclosure: I'm the author)...
This has 2 different features that might help here; for the root object (i.e. the outermost object in a graph) you can supply the existing object directly to either the Merge methods (in v1, also present in v2 for compatibility), or (in v2) the Deserialize methods; for example:
var obj = Serializer.Merge<YourType>(source, instance);
However, in a larger graph, you might want to supply other objects yourself (than just the root). The following is not exposed on the attribute API, but is a new feature in v2:
RuntimeTypeModel.Default[typeof(SomeType)].SetFactory(factoryMethod);
where factoryMethod can be either the name of a static method in SomeType (that returns a SomeType instance), or can be a MethodInfo to any static method anywhere. The method can additionally (optionally) take the serialization-context as a parameter if you want. This method should then be used to supply all new instances of SomeType.
Note: protobuf-net is not quite the same as BinaryFormatter; for best effect, you need to tell it how to map your members - very similar to marking things as [DataMember] for WCF/DataContractSerializer. This can be attributes, but does not need to be.

No problem, just use 2 classes. In getObject method you get your existing object
[Serializable]
public class McRealObjectHelper : IObjectReference, ISerializable
{
Object m_realObject;
virtual object getObject(McObjectId id)
{
return id.GetObject();
}
public McRealObjectHelper(SerializationInfo info, StreamingContext context)
{
McObjectId id = (McObjectId)info.GetValue("ID", typeof(McObjectId));
m_realObject = getObject(id);
if(m_realObject == null)
return;
Type t = m_realObject.GetType();
MemberInfo[] members = FormatterServices.GetSerializableMembers(t, context);
List<MemberInfo> deserializeMembers = new List<MemberInfo>(members.Length);
List<object> data = new List<object>(members.Length);
foreach(MemberInfo mi in members)
{
Type dataType = null;
if(mi.MemberType == MemberTypes.Field)
{
FieldInfo fi = mi as FieldInfo;
dataType = fi.FieldType;
} else if(mi.MemberType == MemberTypes.Property){
PropertyInfo pi = mi as PropertyInfo;
dataType = pi.PropertyType;
}
try
{
if(dataType != null){
data.Add(info.GetValue(mi.Name, dataType));
deserializeMembers.Add(mi);
}
}
catch (SerializationException)
{
//some fiels are missing, new version, skip this fields
}
}
FormatterServices.PopulateObjectMembers(m_realObject, deserializeMembers.ToArray(), data.ToArray());
}
public object GetRealObject( StreamingContext context )
{
return m_realObject;
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
}
}
public class McRealObjectBinder: SerializationBinder
{
String assemVer;
String typeVer;
public McRealObjectBinder(String asmName, String typeName)
{
assemVer = asmName;
typeVer = typeName;
}
public override Type BindToType( String assemblyName, String typeName )
{
Type typeToDeserialize = null;
if ( assemblyName.Equals( assemVer ) && typeName.Equals( typeVer ) )
{
return typeof(McRealObjectHelper);
}
typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) );
return typeToDeserialize;
}
}
Then, when deserialize:
BinaryFormatter bf = new BinaryFormatter(null, context);
bf.Binder = new McRealObjectBinder(YourType.Assembly.FullName, YourType.FullName);
bf.Deserialize(memStream);

If it's just a matter of copying a few fields, I would avoid all the trouble and take the simple route - deserialize into a new instance, then copy the appropriate fields to the existing instance. It will cost you a couple of extra copies, but you'll save a lot of time on debugging.

It's a bit unusual but it works:
[Serializable]
public class Pets
{
public int Cats { get; set; }
public int Dogs { get; set; }
}
public static class Utils
{
public static byte[] BinarySerialize(object o)
{
using (var ms = new MemoryStream())
{
IFormatter f = new BinaryFormatter();
f.Serialize(ms, o);
return ms.ToArray();
}
}
public static dynamic BinaryDeserialize(byte[] bytes, dynamic o)
{
using (var ms = new MemoryStream(bytes))
{
ms.Position = 0;
IFormatter f = new BinaryFormatter();
o = (dynamic)f.Deserialize(ms);
return o;
}
}
}
The deserialization is tricky with the dynamic which cannot be passed by reference.
And finally a no-sense test for proof:
Pets p = new Pets() { Cats = 0, Dogs = 3 };
Console.WriteLine("{0}, {1}", p.Cats, p.Dogs);
byte[] serial = Utils.BinarySerialize(p);
p.Cats = 1;
Console.WriteLine("{0}, {1}", p.Cats, p.Dogs);
p = Utils.BinaryDeserialize(serial, p);
Console.WriteLine("{0}, {1}", p.Cats, p.Dogs);
The output is the following:
0, 3
1, 3
0, 3
Replace memory strings with file stream and you will get the same results.

Related

Specify output format of DataContractJsonSerializer?

For the project at hand I have to use DataContractJsonSerializer for serialization and have to generate a specific output based on the member's values.
My class looks similar to this:
public class MyClass
{
public string foo;
public string bar;
public MyClass(string f, string b = "")
{
this.foo = f;
this.bar = b;
}
}
Now serialization of a list like
var list = new List<MyClass>
{
new MyClass("foo", "bar"),
new MyClass("foo1"),
new MyClass("foo2", "bar2")
};
should look like this
[{"foo": "bar"}, "foo1", {"foo2": "bar2"}]
or - better yet - escaped and as a string:
"[{\"foo\": \"bar\"}, \"foo1\", {\"foo2\": \"bar2\"}]"
A mixture of strings and objects. How could I achieve this?
I tried to override the ToString() method and serializing the corresponding strings resulting in unnecessarily escaped symbols, e.g. bar2 could be m/s and was escaped as m\\/s which could not be deserialized correctly on the web server.
Finally, I just need to serialize to this format. There is no need to deserialize this format with DataContractJsonSerializer.
What you would like to do is to conditionally replace instances of MyClass with a serialization surrogate that is a string or a dictionary, however using a primitive as a surrogate is not supported by data contract serialization, as explained here by Microsoft.
However, since you only need to serialize and not deserialize, you can get the output you need by manually replacing your List<MyClass> with a surrogate List<object> in which instances of MyClass are replaced with a string when bar is empty, and a Dictionary<string, string> otherwise. Then manually construct a DataContractJsonSerializer with the following values in DataContractJsonSerializerSettings:
Set KnownTypes to be a list of the actual types in the surrogate object list.
Set EmitTypeInformation to EmitTypeInformation.Never. This suppresses inclusion of type hints in the output JSON.
Set UseSimpleDictionaryFormat to true.
(Note that DataContractJsonSerializerSettings, EmitTypeInformation and UseSimpleDictionaryFormat are all new to .NET 4.5.)
Thus you could define your MyType as follows:
public interface IHasSerializationSurrogate
{
object ToSerializationSurrogate();
}
public class MyClass : IHasSerializationSurrogate
{
public string foo;
public string bar;
// If you're not going to mark MyClass with data contract attributes, DataContractJsonSerializer
// requires a default constructor. It can be private.
MyClass() : this("", "") { }
public MyClass(string f, string b = "")
{
this.foo = f;
this.bar = b;
}
public object ToSerializationSurrogate()
{
if (string.IsNullOrEmpty(bar))
return foo;
return new Dictionary<string, string> { { foo, bar } };
}
}
Then introduce the following extension methods:
public static partial class DataContractJsonSerializerHelper
{
public static string SerializeJsonSurrogateCollection<T>(this IEnumerable<T> collection) where T : IHasSerializationSurrogate
{
if (collection == null)
throw new ArgumentNullException();
var surrogate = collection.Select(i => i == null ? null : i.ToSerializationSurrogate()).ToList();
var settings = new DataContractJsonSerializerSettings
{
EmitTypeInformation = EmitTypeInformation.Never,
KnownTypes = surrogate.Where(s => s != null).Select(s => s.GetType()).Distinct().ToList(),
UseSimpleDictionaryFormat = true,
};
return DataContractJsonSerializerHelper.SerializeJson(surrogate, settings);
}
public static string SerializeJson<T>(this T obj, DataContractJsonSerializerSettings settings)
{
var type = obj == null ? typeof(T) : obj.GetType();
var serializer = new DataContractJsonSerializer(type, settings);
return SerializeJson<T>(obj, serializer);
}
public static string SerializeJson<T>(this T obj, DataContractJsonSerializer serializer = null)
{
serializer = serializer ?? new DataContractJsonSerializer(obj == null ? typeof(T) : obj.GetType());
using (var memory = new MemoryStream())
{
serializer.WriteObject(memory, obj);
memory.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(memory))
{
return reader.ReadToEnd();
}
}
}
}
And serialize your list to JSON manually as follows:
var json = list.SerializeJsonSurrogateCollection();
With the following result:
[{"foo":"bar"},"foo1",null,{"foo2":"bar2"}]
If you really need the string to be escaped (why?) you can always serialize the resulting string to JSON a second time with DataContractJsonSerializer producing a double-serialized result:
var jsonOfJson = json.SerializeJson();
Resulting in
"[{\"foo\":\"bar\"},\"foo1\",{\"foo2\":\"bar2\"}]"
Deserialize will happen based on name and value combination so your text should be like this
[{"foo": "foo","bar": "bar"},{"foo": "foo1"},{"foo": "foo2","bar": "bar2"}]

Ignore XML element order during deserialization [duplicate]

I'm trying to implement a client for a service with a really deficient spec. It's SOAP-like, although it has no WSDL or equivalent file. The spec also doesn't provide any information about the correct ordering of elements - they're listed alphabetically in the spec, but the service returns an XML parse error if they're out of order in the request (said order to be derived by examining the examples).
I can work with this for submitting requests, even if it's a pain. However, I don't know how to handle responses correctly.
With both SoapEnvelope and directly with XmlSerializer, if the response contains an element I haven't yet ordered correctly, it shows up as null on my object. Once again, I can manage to work with this, and manually order the class properties with Order attributes, but I have no way to tell whether the original XML has a field that I didn't order correctly and thus got left as null.
This leads me to the current question: How can I check if the XmlSerializer dropped a field?
You can use the XmlSerializer.UnknownElement event on XmlSerializer to capture out-of-order elements. This will allow you to manually find and fix problems in deserialization.
A more complex answer would be to correctly order your elements when serializing, but ignore order when deserializing. This requires using the XmlAttributes class and the XmlSerializer(Type, XmlAttributeOverrides) constructor. Note that serializers constructed in this manner must be cached in a hash table and resused to avoid a severe memory leak, and thus this solution is a little "finicky" since Microsoft doesn't provide a meaningful GetHashCode() for XmlAttributeOverrides. The following is one possible implementation which depends upon knowing in advance all types that need their XmlElementAttribute.Order and XmlArrayAttribute.Order properties ignored, thus avoiding the need to create a complex custom hashing method:
// https://stackoverflow.com/questions/33506708/deserializing-xml-with-unknown-element-order
public class XmlSerializerFactory : XmlOrderFreeSerializerFactory
{
static readonly XmlSerializerFactory instance;
// Use a static constructor for lazy initialization.
private XmlSerializerFactory()
: base(new[] { typeof(Type2), typeof(Type1), typeof(TestClass), typeof(Type3) }) // These are the types in your client for which Order needs to be ignored whend deserializing
{
}
static XmlSerializerFactory()
{
instance = new XmlSerializerFactory();
}
public static XmlSerializerFactory Instance { get { return instance; } }
}
public abstract class XmlOrderFreeSerializerFactory
{
readonly XmlAttributeOverrides overrides;
readonly object locker = new object();
readonly Dictionary<Type, XmlSerializer> serializers = new Dictionary<Type, XmlSerializer>();
static void AddOverrideAttributes(Type type, XmlAttributeOverrides overrides)
{
if (type == null || type == typeof(object) || type.IsPrimitive || type == typeof(string))
return;
var mask = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
foreach (var member in type.GetProperties(mask).Cast<MemberInfo>().Union(type.GetFields(mask)))
{
XmlAttributes overrideAttr = null;
foreach (var attr in member.GetCustomAttributes<XmlElementAttribute>())
{
overrideAttr = overrideAttr ?? new XmlAttributes();
overrideAttr.XmlElements.Add(new XmlElementAttribute { DataType = attr.DataType, ElementName = attr.ElementName, Form = attr.Form, IsNullable = attr.IsNullable, Namespace = attr.Namespace, Type = attr.Type });
}
foreach (var attr in member.GetCustomAttributes<XmlArrayAttribute>())
{
overrideAttr = overrideAttr ?? new XmlAttributes();
overrideAttr.XmlArray = new XmlArrayAttribute { ElementName = attr.ElementName, Form = attr.Form, IsNullable = attr.IsNullable, Namespace = attr.Namespace };
}
foreach (var attr in member.GetCustomAttributes<XmlArrayItemAttribute>())
{
overrideAttr = overrideAttr ?? new XmlAttributes();
overrideAttr.XmlArrayItems.Add(attr);
}
foreach (var attr in member.GetCustomAttributes<XmlAnyElementAttribute>())
{
overrideAttr = overrideAttr ?? new XmlAttributes();
overrideAttr.XmlAnyElements.Add(new XmlAnyElementAttribute { Name = attr.Name, Namespace = attr.Namespace });
}
if (overrideAttr != null)
overrides.Add(type, member.Name, overrideAttr);
}
}
protected XmlOrderFreeSerializerFactory(IEnumerable<Type> types)
{
overrides = new XmlAttributeOverrides();
foreach (var type in types.SelectMany(t => t.BaseTypesAndSelf()).Distinct())
{
AddOverrideAttributes(type, overrides);
}
}
public XmlSerializer GetSerializer(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
lock (locker)
{
XmlSerializer serializer;
if (!serializers.TryGetValue(type, out serializer))
serializers[type] = serializer = new XmlSerializer(type, overrides);
return serializer;
}
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
Then when deserializing a type, use the XmlSerializer provided by the factory. Given that SoapEnvelope is a subclass of XmlDocument, you should be able to deserialize the body node along the lines of the answer in Deserialize object property with StringReader vs XmlNodeReader.
Note -- only moderately tested. Demo fiddle here.

Web API OData media type formatter when using $expand

I'm trying to create a MediaTypeFormatter to handle text/csv but running into a few problems when using $expand in the OData query.
Query:
http://localhost/RestBlog/api/Blogs/121?$expand=Comments
Controller:
[EnableQuery]
public IQueryable<Blog> GetBlog(int id)
{
return DbCtx.Blog.Where(x => x.blogID == id);
}
In my media type formatter:
private static MethodInfo _createStreamWriter =
typeof(CsvFormatter)
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(m => m.Name == "StreamWriter");
internal static void StreamWriter<T, X>(T results)
{
var queryableResult = results as IQueryable<X>;
if (queryableResult != null)
{
var actualResults = queryableResult.ToList<X>();
}
}
public override void WriteToStream(Type type, object value,
Stream writeStream, HttpContent content)
{
Type genericType = type.GetGenericArguments()[0];
_createStreamWriter.MakeGenericMethod(
new Type[] { value.GetType(), genericType })
.Invoke(null, new object[] { value }
);
}
Note that the type of value is System.Data.Entity.Infrastructure.DbQuery<System.Web.Http.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand<Rest.Blog>> which means that it doesn't work.
The type of value should be IQueryable but upon casting it returns null.
When making a query without the $expand things work a lot more sensibly. What am I doing wrong?
I'm just trying to get at the data before even outputting as CSV, so guidance would be greatly appreciated.
If you look at the source code for OData Web API, you will see that
SelectExpandBinder.SelectAllAndExpand is a subclass of the generic class SelectExpandWrapper(TEntity) :
private class SelectAllAndExpand<TEntity> : SelectExpandWrapper<TEntity>
{
}
which itself is a subclass of non-generic SelectExpandWrapper:
internal class SelectExpandWrapper<TElement> : SelectExpandWrapper
{
// Implementation...
}
which in turn implements IEdmEntityObject and ISelectExpandWrapper:
internal abstract class SelectExpandWrapper : IEdmEntityObject, ISelectExpandWrapper
{
// Implementation...
}
This means that you have access to the ISelectExpandWrapper.ToDictionary method and can use it to get at the properties of the underlying entity:
public interface ISelectExpandWrapper
{
IDictionary<string, object> ToDictionary();
IDictionary<string, object> ToDictionary(Func<IEdmModel, IEdmStructuredType, IPropertyMapper> propertyMapperProvider);
}
Indeed this is how serialization to JSON is implemented in the framework as can be seen from SelectExpandWrapperConverter:
internal class SelectExpandWrapperConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ISelectExpandWrapper selectExpandWrapper = value as ISelectExpandWrapper;
if (selectExpandWrapper != null)
{
serializer.Serialize(writer, selectExpandWrapper.ToDictionary(_mapperProvider));
}
}
// Other methods...
}
I was googled when i face that issue in my task.. i have clean implementation from this thread
first you need verify edm modelbuilder in proper way for expand
objects
you have to register edm model for blog and foreign key releations.then only it will sucess
Example
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Blog>("blog");
builder.EntitySet<Profile>("profile");//ForeignKey releations of blog
builder.EntitySet<user>("user");//ForeignKey releations of profile
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
Then you need develop this formatter ..example source code us here
applogies for my english..
First of all we need to create a class which will be derived from MediaTypeFormatter abstract class. Here is the class with its constructors:
public class CSVMediaTypeFormatter : MediaTypeFormatter {
public CSVMediaTypeFormatter() {
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
}
public CSVMediaTypeFormatter(
MediaTypeMapping mediaTypeMapping) : this() {
MediaTypeMappings.Add(mediaTypeMapping);
}
public CSVMediaTypeFormatter(
IEnumerable<MediaTypeMapping> mediaTypeMappings) : this() {
foreach (var mediaTypeMapping in mediaTypeMappings) {
MediaTypeMappings.Add(mediaTypeMapping);
}
}
}
Above, no matter which constructor you use, we always add text/csv media type to be supported for this formatter. We also allow custom MediaTypeMappings to be injected.
Now, we need to override two methods: MediaTypeFormatter.CanWriteType and MediaTypeFormatter.OnWriteToStreamAsync.
First of all, here is the CanWriteType method implementation. What this method needs to do is to determine if the type of the object is supported with this formatter or not in order to write it.
protected override bool CanWriteType(Type type) {
if (type == null)
throw new ArgumentNullException("type");
return isTypeOfIEnumerable(type);
}
private bool isTypeOfIEnumerable(Type type) {
foreach (Type interfaceType in type.GetInterfaces()) {
if (interfaceType == typeof(IEnumerable))
return true;
}
return false;
}
What this does here is to check if the object has implemented the IEnumerable interface. If so, then it is cool with that and can format the object. If not, it will return false and framework will ignore this formatter for that particular request.
And finally, here is the actual implementation. We need to do some work with reflection here in order to get the property names and values out of the value parameter which is a type of object:
protected override Task OnWriteToStreamAsync(
Type type,
object value,
Stream stream,
HttpContentHeaders contentHeaders,
FormatterContext formatterContext,
TransportContext transportContext) {
writeStream(type, value, stream, contentHeaders);
var tcs = new TaskCompletionSource<int>();
tcs.SetResult(0);
return tcs.Task;
}
private void writeStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders) {
//NOTE: We have check the type inside CanWriteType method
//If request comes this far, the type is IEnumerable. We are safe.
Type itemType = type.GetGenericArguments()[0];
StringWriter _stringWriter = new StringWriter();
_stringWriter.WriteLine(
string.Join<string>(
",", itemType.GetProperties().Select(x => x.Name )
)
);
foreach (var obj in (IEnumerable<object>)value) {
var vals = obj.GetType().GetProperties().Select(
pi => new {
Value = pi.GetValue(obj, null)
}
);
string _valueLine = string.Empty;
foreach (var val in vals) {
if (val.Value != null) {
var _val = val.Value.ToString();
//Check if the value contans a comma and place it in quotes if so
if (_val.Contains(","))
_val = string.Concat("\"", _val, "\"");
//Replace any \r or \n special characters from a new line with a space
if (_val.Contains("\r"))
_val = _val.Replace("\r", " ");
if (_val.Contains("\n"))
_val = _val.Replace("\n", " ");
_valueLine = string.Concat(_valueLine, _val, ",");
} else {
_valueLine = string.Concat(string.Empty, ",");
}
}
_stringWriter.WriteLine(_valueLine.TrimEnd(','));
}
var streamWriter = new StreamWriter(stream);
streamWriter.Write(_stringWriter.ToString());
}
We are partially done. Now, we need to make use out of this. I registered this formatter into the pipeline with the following code inside Global.asax Application_Start method:
GlobalConfiguration.Configuration.Formatters.Add(
new CSVMediaTypeFormatter(
new QueryStringMapping("format", "csv", "text/csv")
)
);
On my sample application, when you navigate to /api/cars?format=csv, it will get you a CSV file but without an extension. Go ahead and add the csv extension. Then, open it with Excel and you should see something similar to below:

How to use protobuf-net with immutable value types?

Suppose I have an immutable value type like this:
[Serializable]
[DataContract]
public struct MyValueType : ISerializable
{
private readonly int _x;
private readonly int _z;
public MyValueType(int x, int z)
: this()
{
_x = x;
_z = z;
}
// this constructor is used for deserialization
public MyValueType(SerializationInfo info, StreamingContext text)
: this()
{
_x = info.GetInt32("X");
_z = info.GetInt32("Z");
}
[DataMember(Order = 1)]
public int X
{
get { return _x; }
}
[DataMember(Order = 2)]
public int Z
{
get { return _z; }
}
public static bool operator ==(MyValueType a, MyValueType b)
{
return a.Equals(b);
}
public static bool operator !=(MyValueType a, MyValueType b)
{
return !(a == b);
}
public override bool Equals(object other)
{
if (!(other is MyValueType))
{
return false;
}
return Equals((MyValueType)other);
}
public bool Equals(MyValueType other)
{
return X == other.X && Z == other.Z;
}
public override int GetHashCode()
{
unchecked
{
return (X * 397) ^ Z;
}
}
// this method is called during serialization
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("X", X);
info.AddValue("Z", Z);
}
public override string ToString()
{
return string.Format("[{0}, {1}]", X, Z);
}
}
It works with BinaryFormatter or DataContractSerializer but when I try to use it with protobuf-net (http://code.google.com/p/protobuf-net/) serializer I get this error:
Cannot apply changes to property
ConsoleApplication.Program+MyValueType.X
If I apply setters to the properties marked with DataMember attribute it will work but then it breaks immutability of this value type and that's not desirable to us.
Does anyone know what I need to do to get it work? I've noticed that there's an overload of the ProtoBu.Serializer.Serialize method which takes in a SerializationInfo and a StreamingContext but I've not use them outside of the context of implementing ISerializable interface, so any code examples on how to use them in this context will be much appreciated!
Thanks,
EDIT: so I dug up some old MSDN article and got a better understanding of where and how SerializationInfo and StreamingContext is used, but when I tried to do this:
var serializationInfo = new SerializationInfo(
typeof(MyValueType), new FormatterConverter());
ProtoBuf.Serializer.Serialize(serializationInfo, valueType);
it turns out that the Serialize<T> method only allows reference types, is there a particular reason for that? It seems a little strange given that I'm able to serialize value types exposed through a reference type.
Which version of protobuf-net are you using? If you are the latest v2 build, it should cope with this automatically. In case I haven't deployed this code yet, I'll update the download areas in a moment, but essentially if your type is unadorned (no attributes), it will detect the common "tuple" patten you are using, and decide (from the constructor) that x (constructor parameter)/X (property) is field 1, and z/Z is field 2.
Another approach is to mark the fields:
[ProtoMember(1)]
private readonly int _x;
[ProtoMember(2)]
private readonly int _z;
(or alternatively [DataMember(Order=n)] on the fields)
which should work, depending on the trust level. What I haven't done yet is generalise the constructor code to attributed scenarios. That isn't hard, but I wanted to push the basic case first, then evolve it.
I've added the following two samples/tests with full code here:
[Test]
public void RoundTripImmutableTypeAsTuple()
{
using(var ms = new MemoryStream())
{
var val = new MyValueTypeAsTuple(123, 456);
Serializer.Serialize(ms, val);
ms.Position = 0;
var clone = Serializer.Deserialize<MyValueTypeAsTuple>(ms);
Assert.AreEqual(123, clone.X);
Assert.AreEqual(456, clone.Z);
}
}
[Test]
public void RoundTripImmutableTypeViaFields()
{
using (var ms = new MemoryStream())
{
var val = new MyValueTypeViaFields(123, 456);
Serializer.Serialize(ms, val);
ms.Position = 0;
var clone = Serializer.Deserialize<MyValueTypeViaFields>(ms);
Assert.AreEqual(123, clone.X);
Assert.AreEqual(456, clone.Z);
}
}
Also:
it turns out that the Serialize method only allows reference types
yes, that was a design limitation of v1 that related to the boxing model etc; this no longer applies with v2.
Also, note that protobuf-net doesn't itself consume ISerializable (although it can be used to implement ISerializable).
The selected answer didn't work for me since the link is broken and I cannot see the MyValueTypeViaFields code.
In any case I have had the same exception No parameterless constructor found for my class:
[ProtoContract]
public class FakeSimpleEvent
: IPersistableEvent
{
[ProtoMember(1)]
public Guid AggregateId { get; }
[ProtoMember(2)]
public string Value { get; }
public FakeSimpleEvent(Guid aggregateId, string value)
{
AggregateId = aggregateId;
Value = value;
}
}
when deserializing it with the following code:
public class BinarySerializationService
: IBinarySerializationService
{
public byte[] ToBytes(object obj)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
using (var memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, obj);
var bytes = memoryStream.ToArray();
return bytes;
}
}
public TType FromBytes<TType>(byte[] bytes)
where TType : class
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
var type = typeof(TType);
var result = FromBytes(bytes, type);
return (TType)result;
}
public object FromBytes(byte[] bytes, Type type)
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
int length = bytes.Length;
using (var memoryStream = new MemoryStream())
{
memoryStream.Write(bytes, 0, length);
memoryStream.Seek(0, SeekOrigin.Begin);
var obj = Serializer.Deserialize(type, memoryStream);
return obj;
}
}
}
being called like var dataObject = (IPersistableEvent)_binarySerializationService.FromBytes(data, eventType);
My message class FakeSimpleEvent has indeed parameterless constructor because I want it immutable.
I am using protobuf-net 2.4.0 and I can confirm that it supports complex constructors and immutable message classes. Simply use the following decorator
[ProtoContract(SkipConstructor = true)]
If true, the constructor for the type is bypassed during
deserialization, meaning any field initializers or other
initialization code is skipped.
UPDATE 1: (20 June 2019)
I don't like polluting my classes with attributes that belong to protobuffer because the domain model should be technology-agnostic (other than dotnet framework's types of course)
So for using protobuf-net with message classes without attributes and without parameterless constructor (i.e: immutable) you can have the following:
public class FakeSimpleEvent
: IPersistableEvent
{
public Guid AggregateId { get; }
public string Value { get; }
public FakeSimpleEvent(Guid aggregateId, string value)
{
AggregateId = aggregateId;
Value = value;
}
}
and then configure protobuf with the following for this class.
var fakeSimpleEvent = RuntimeTypeModel.Default.Add(typeof(FakeSimpleEvent), false);
fakeSimpleEvent.Add(1, nameof(FakeSimpleEvent.AggregateId));
fakeSimpleEvent.Add(2, nameof(FakeSimpleEvent.Value));
fakeSimpleEvent.UseConstructor = false;
This would be the equivalent to my previous answer but much cleaner.
PS: Don't mind the IPersistableEvent. It's irrelevant for the example, just a marker interface I use somewhere else

C# DataContract Serialization, how to deserialize to already existing instance

I have a class, which holds a static dictionary of all existing instances, which are defined at compile time.
Basically it looks like this:
[DataContract]
class Foo
{
private static Dictionary<long, Foo> instances = new Dictionary<long, Foo>();
[DataMember]
private long id;
public static readonly Foo A = Create(1);
public static readonly Foo B = Create(2);
public static readonly Foo C = Create(3);
private static Foo Create(long id)
{
Foo instance = new Foo();
instance.id = id;
instances.Add(instance);
return instance;
}
public static Foo Get(long id)
{
return instances[id];
}
}
There are other fields, and the class is derived, but this doesn't matter for the problem.
Only the id is serialized. When an instance of this type is deserialized, I would like to get the instance that has been created as the static field (A, B or C), using Foo.Get(id) instead of getting a new instance.
Is there a simple way to do this? I didn't find any resources which I was able to understand.
During deserialization it (AFAIK) always uses a new object (FormatterServices.GetUninitializedObject), but to get it to substitute the objects after deserialization (but before they are returned to the caller), you can implement IObjectReference, like so:
[DataContract]
class Foo : IObjectReference { // <===== implement an extra interface
object IObjectReference.GetRealObject(StreamingContext ctx) {
return Get(id);
}
...snip
}
done... proof:
static class Program {
static void Main() {
Foo foo = Foo.Get(2), clone;
DataContractSerializer ser = new DataContractSerializer(typeof(Foo));
using (MemoryStream ms = new MemoryStream()) { // clone it via DCS
ser.WriteObject(ms, foo);
ms.Position = 0;
clone = (Foo)ser.ReadObject(ms);
}
Console.WriteLine(ReferenceEquals(foo, clone)); // true
}
}
Note there are some extra notes on this for partial trust scenarios on MSDN, here.
I had a similar problem and the best solution that I found was adding some wrapper class, that was managing instances of the one needed to be serialized.
I am not sure about the exact signature with Contracts. I used SerializableAttribute, and with it i looked smth. like that:
[Serializable]
class FooSerializableWrapper : ISerializable
{
private readonly long id;
public Foo Foo
{
get
{
return Foo.Get(id);
}
}
public FooSerializableWrapper(Foo foo)
{
id = foo.id;
}
protected FooSerializableWrapper(SerializationInfo info, StreamingContext context)
{
id = info.GetInt64("id");
}
void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("id", id);
}
}
You may be able to get a step towards what you are looking for using OnDeserializingAttribute. However, that will probably only let you set properties (so you could have what amounts to a Copy method that populates all the properties of the current instance using your static instance.
I think if you actually want to return your static instances, you'd probably have to write your own Deserializer...
Untested, but I would assume you could implement a deserializer pretty easily like this:
public class MyDeserializer : System.Xml.Serialization.XmlSerializer
{
protected override object Deserialize(System.Xml.Serialization.XmlSerializationReader reader)
{
Foo obj = (Foo)base.Deserialize(reader);
return Foo.Get(obj.id);
}
}
Note that you'll have to do something about getting the ID since it is private in your code; Also this assumes you are using XML serialization; Replace the inheritance with whatever you actually are using. And finally, this means you'll have to instantiate this type when deserializing your objects, which may involve changing some code and/or configuration.
No problem, just use 2 classes. In getObject method you get your existing object
[Serializable]
public class McRealObjectHelper : IObjectReference, ISerializable
{
Object m_realObject;
virtual object getObject(McObjectId id)
{
return id.GetObject();
}
public McRealObjectHelper(SerializationInfo info, StreamingContext context)
{
McObjectId id = (McObjectId)info.GetValue("ID", typeof(McObjectId));
m_realObject = getObject(id);
if(m_realObject == null)
return;
Type t = m_realObject.GetType();
MemberInfo[] members = FormatterServices.GetSerializableMembers(t, context);
List<MemberInfo> deserializeMembers = new List<MemberInfo>(members.Length);
List<object> data = new List<object>(members.Length);
foreach(MemberInfo mi in members)
{
Type dataType = null;
if(mi.MemberType == MemberTypes.Field)
{
FieldInfo fi = mi as FieldInfo;
dataType = fi.FieldType;
} else if(mi.MemberType == MemberTypes.Property){
PropertyInfo pi = mi as PropertyInfo;
dataType = pi.PropertyType;
}
try
{
if(dataType != null){
data.Add(info.GetValue(mi.Name, dataType));
deserializeMembers.Add(mi);
}
}
catch (SerializationException)
{
//some fiels are missing, new version, skip this fields
}
}
FormatterServices.PopulateObjectMembers(m_realObject, deserializeMembers.ToArray(), data.ToArray());
}
public object GetRealObject( StreamingContext context )
{
return m_realObject;
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
}
}
public class McRealObjectBinder: SerializationBinder
{
String assemVer;
String typeVer;
public McRealObjectBinder(String asmName, String typeName)
{
assemVer = asmName;
typeVer = typeName;
}
public override Type BindToType( String assemblyName, String typeName )
{
Type typeToDeserialize = null;
if ( assemblyName.Equals( assemVer ) && typeName.Equals( typeVer ) )
{
return typeof(McRealObjectHelper);
}
typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) );
return typeToDeserialize;
}
}
Then, when deserialize:
BinaryFormatter bf = new BinaryFormatter(null, context);
bf.Binder = new McRealObjectBinder(YourType.Assembly.FullName, YourType.FullName);
bf.Deserialize(memStream);

Categories

Resources