mbraceproject / FsPickler

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

Serializing union and record types declared in F# PCLs #47

Closed pragmatrix closed 9 years ago

pragmatrix commented 9 years ago

It seems that F# union and record types can not be serialized when they are declared in an F# portable class library. I am getting NonSerializableTypeExceptions even when I want to serialize simple instances of type Test = | Test for example.

The F# compiler does not attach the [Serializable] attribute to these types, because it's unsupported in PCLs.

Is there any way to enforce the generation of the Picklers that are required for these types?

eiriktsarpalis commented 9 years ago

Hi, can you tell me what happens if you explicitly attach the [<Serializable>] or [<AutoSerializable(true)>] attributes to the types?

pragmatrix commented 9 years ago

Attaching [<Serializable>] is not possible, because it is not defined for PCLs, and when [<AutoSerializable(true)>] is attached, it fails the same way:

[<AutoSerializableAttribute(true)>]
type Test = | Test
Nessos.FsPickler.NonSerializableTypeException : Type 'L14.Drawing.Test' is not serializable.
   at Nessos.FsPickler.Utils.Exn`1.get_Value() in c:\Users\eirik\Development\nessos\FsPickler\src\FsPickler\Utils\Utils.fs:line 54
   at Nessos.FsPickler.PicklerCache.Nessos-FsPickler-IPicklerResolver-Resolve[T]() in c:\Users\eirik\Development\nessos\FsPickler\src\FsPickler\PicklerGeneration\PicklerCache.fs:line 55
   at Nessos.FsPickler.FsPicklerSerializer.Serialize[T](Stream stream, T value, FSharpOption`1 pickler, FSharpOption`1 streamingContext, FSharpOption`1 encoding, FSharpOption`1 leaveOpen) in c:\Users\eirik\Development\nessos\FsPickler\src\FsPickler\FsPickler\Serializer.fs:line 52
   at <StartupCode$FsPickler>.$Serializer.Pickle@147.Invoke(T v) in c:\Users\eirik\Development\nessos\FsPickler\src\FsPickler\FsPickler\Serializer.fs:line 147
   at Nessos.FsPickler.FsPicklerSerializer.Pickle[T](T value, FSharpOption`1 pickler, FSharpOption`1 streamingContext, FSharpOption`1 encoding) in c:\Users\eirik\Development\nessos\FsPickler\src\FsPickler\FsPickler\Serializer.fs:line 147
   at SerializationTests.TestOTest.pickleTest() in C:\.....\l14\SerializationTests\Library1.fs:line 12
pragmatrix commented 9 years ago

Here is the decompiled header of the class:

[AutoSerializable(true)]
  [DebuggerDisplay("{__DebugDisplay(),nq}")]
  [CompilationMapping(SourceConstructFlags.SumType)]
  [StructLayout(LayoutKind.Auto, CharSet = (CharSet) 4)]
  public class Test : IEquatable<Test>, IStructuralEquatable, IComparable<Test>, IComparable, IStructuralComparable
eiriktsarpalis commented 9 years ago

I see. In the current version you could circumvent this by defining a custom pickler.

Given that this might be too noisy a solution, it could be possible to add an attribute in FsPickler that forces this behaviour. Something like

[<EnsureSerializable>]
type Test = Test of string
pragmatrix commented 9 years ago

But then FsPickler would need to be referenced from the PCL, which is not possible at the moment. And I also would not like to see this requirement in the future.

Something like a global FsPickler.AssumeSerializable<Test>(recursive : bool) or a way to dynamically attach attributes to types may be needed here.

eiriktsarpalis commented 9 years ago

I see. Ok so we may have to go down the TypeDescriptor path.

pragmatrix commented 9 years ago

After I failed to use Newtonsoft.Json as a stand-in, I've taken a look at the master branch today, saw FsPickler.DeclareSerializable(), and tried it. So far this works quite well. Thanks!

eiriktsarpalis commented 9 years ago

I need to add some more tests before this makes it into a release, should be out soon.