pocketberserker / MessagePack.FSharpExtensions

MessagePack Extensions for F#. / msgpack.org[F#]
MIT License
43 stars 7 forks source link

Serializing discriminated union failed while using non-generic classes #3

Open chnlkw opened 5 years ago

chnlkw commented 5 years ago

Problem

I get an error when serializing A in SimpleUnion

[<MessagePackObject>]
type SimpleUnion =
  | A
  | B of int
  | C of int64 * float32

let resolver = WithFSharpDefaultResolver() :> IFormatterResolver
MessagePackSerializer.NonGeneric.Serialize(A.GetType(), A, resolver) // failed

The exception is

Message: System.TypeLoadException : Type 
'MessagePack.FSharp.Formatters.MessagePack_Tests_DUTest\+SimpleUnion\+_AFormatter4' 
from assembly 'MessagePack.FSharp.DynamicUnionResolver, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
 is attempting to implement an inaccessible interface.

But it can serialize A correctly with SimpleUnion2 which inserts a unit field.

[<MessagePackObject>]
type SimpleUnion2 =
  | A2
  | AU2 of unit
  | B2 of int
  | C2 of int64 * float32

Here is my test case source

module MessagePack.Tests.DUTest

open Xunit
open MessagePack

let convert_nongeneric (value: 'a) =
  let resolver = WithFSharpDefaultResolver() :> IFormatterResolver
  let t = value.GetType()
  let bin : byte[] = MessagePackSerializer.NonGeneric.Serialize(t, value :> obj, resolver)
  let actual :'a = MessagePackSerializer.NonGeneric.Deserialize(t, bin, resolver) :?> 'a
  Assert.Equal<'a>(value, actual)

[<MessagePackObject>]
type SimpleUnion =
  | A
  | B of int
  | C of int64 * float32

[<MessagePackObject>]
type SimpleUnion2 =
  | A2
  | AU2 of unit
  | B2 of int
  | C2 of int64 * float32

[<Fact>]
let sampleA () =
    convert_nongeneric A  // failed

[<Fact>]
let ``sampleA2`` () =
    convert_nongeneric A2  // success

[<Fact>]
let sampleAU () =
    convert_nongeneric <| AU2 ()

[<Fact>]
let sampleB () =
    convert_nongeneric (B 1)
    convert_nongeneric (B2 1)

[<Fact>]
let sampleC () =
    convert_nongeneric <| C(1L, 1.0f)
    convert_nongeneric <| C2(1L, 1.0f)

Motivation

I am trying to porting this extension to Akka.Serialization.MessagePack, which uses MessagePackSerializer.NonGeneric to serialize a object. This serialize function needs a given type value, which is get from obj.GetType() in Akka implementation. I use the serializaion here

pocketberserker commented 4 years ago

Sorry for the delay in replying...

FSharp.Reflection.FSharpType.IsUnion(SimpleUnion.A.GetType()) returns true, so DiscremenatedUnionResolver will attempt to generate a formatter. However, SimpleUnion.A.GetType() returns type info of the concrete class generated by the compiler, MessagePackSerializer requires IMessagePackFormatter<SimpleUnion.A>.

So far I have not found a way to solve this problem within DiscremenatedUnionResolver.

Please try to check and use Type.BaseType:

let t =
    let t = A.GetType()
    match t.BaseType with
    | null -> t
    | p when Reflection.FSharpType.IsUnion(p) -> p
    | _ -> t
MessagePackSerializer.NonGeneric.Serialize(t, A, resolver)