mbraceproject / FsPickler

A fast multi-format message serializer for .NET
http://mbraceproject.github.io/FsPickler/
MIT License
324 stars 52 forks source link

Cannot serialize an instance of type marked with [<DataContract>] attribute #30

Closed vasily-kirichenko closed 9 years ago

vasily-kirichenko commented 9 years ago

It works OK for a plane type:

#r @"..\..\packages\FsPickler.1.0.4\lib\net45\FsPickler.dll"

open System.IO
open Nessos.FsPickler

type Hash(value: byte[]) =
    member __.Value = value
    override x.ToString() = sprintf "%A" x.Value

let pickler = FsPickler.CreateBinary()
let stream = new MemoryStream()
let h = Hash "1234567890ABCDEF"B
pickler.Serialize(stream, h, leaveOpen = true) 
stream.Position <- 0L
let h': Hash = pickler.Deserialize stream
h'.Value
// val it : byte [] =
//  [|49uy; 50uy; 51uy; 52uy; 53uy; 54uy; 55uy; 56uy; 57uy; 48uy; 65uy; 66uy;
//    67uy; 68uy; 69uy; 70uy|]

Now add [<DataContract>]:

#r @"..\..\packages\FsPickler.1.0.4\lib\net45\FsPickler.dll"
#r "System.Runtime.Serialization"

open System.IO
open Nessos.FsPickler
open System.Runtime.Serialization

[<DataContract>] // <===== here
type Hash(value: byte[]) =
    member __.Value = value
    override x.ToString() = sprintf "%A" x.Value

let pickler = FsPickler.CreateBinary()
let stream = new MemoryStream()
let h = Hash "1234567890ABCDEF"B
pickler.Serialize(stream, h, leaveOpen = true) 
stream.Position <- 0L
let h': Hash = pickler.Deserialize stream
h'.Value
// val it : byte [] = null 
eiriktsarpalis commented 9 years ago

Hi Vasily,

you need to add a DataMember attribute on the .Value property.

vasily-kirichenko commented 9 years ago

DataMember requires writable property in F#. Actually, I encountered this issue when I tried to serialize an instance of a C# class which has a readonly field marked with DataMember. I created a minimal repro:

[DataContract]
public class Hash
{
    [DataMember]
    public readonly byte[] Value;

    public Hash(byte[] value)
    {
        Value = value;
    }
}
#r @"..\packages\FsPickler.1.0.4\lib\net45\FsPickler.dll"
#r "System.Runtime.Serialization"
#r @"..\ClassLibrary1\bin\Debug\ClassLibrary1.dll"

open System.IO
open Nessos.FsPickler
open System.Runtime.Serialization
open ClassLibrary1

let h = Hash "1234567890ABCDEF"B

module DCS =
    let dcs = DataContractSerializer(typeof<Hash>)
    let stream = new MemoryStream()
    dcs.WriteObject(stream, h)
    stream.Position <- 0L
    let h' = dcs.ReadObject stream :?> Hash
    h'.Value
// val it : byte [] =
//  [|49uy; 50uy; 51uy; 52uy; 53uy; 54uy; 55uy; 56uy; 57uy; 48uy; 65uy; 66uy;
//    67uy; 68uy; 69uy; 70uy|]
module FsPickler =
    let pickler = FsPickler.CreateBinary()
    let stream = new MemoryStream()
    pickler.Serialize(stream, h, leaveOpen = true) 
    stream.Position <- 0L
    let h': Hash = pickler.Deserialize stream
    h'.Value
// val it : byte [] = null
eiriktsarpalis commented 9 years ago

Yes, I thought it appropriate to only support properties in DataContract serialization. Maybe that should be changed?

vasily-kirichenko commented 9 years ago

I think FsPickler should be able to serialize everything DataContractSerializer can (if a type is marked with DataContract attribute and members - with DataMember one). Or, as a minimum, it should throw an exception if it sees DataMember on an unsupported member.

eiriktsarpalis commented 9 years ago

Agreed, compatibility is a priority for this library.

eiriktsarpalis commented 9 years ago

Fixed in 81c2e78c0d5d75148d20b62595b7d3bfcd30deef