Serializing F# discriminated unions with protobuf

前端 未结 3 994
有刺的猬
有刺的猬 2020-12-16 04:27

Is there some way to get protobuf to serialize/deserialize F#\'s discriminated unions?

I\'m trying to serialize messages with protobuf. Messages are F# records and d

3条回答
  •  半阙折子戏
    2020-12-16 04:45

    I spiked using protobuf-net for event sourcing DUs and am very appreciative of json.net v6's seamless support for DUs.

    The reasons I relented on my initial desire to use protobuf-net in preference are:

    1. I never got to prove the perf gap I was seeking
    2. My desire to be resilient against field renaming (relying on addressing being via []) in my message contracts are mitigated by the combination of:

      • field name aliasing (i.e. using attributed to tell F# to compile under the old name)
      • the ability to use the strengths of DUs Pattern matching to version events by adding in EventXXXV2 alongside EventXxx in the same DU

    And I didnt find a cleaner way than:

    let registerSerializableDuInModel<'TMessage> (model:RuntimeTypeModel) =
        let baseType = model.[typeof<'TMessage>]
        for case in typeof<'TMessage> |> FSharpType.GetUnionCases do
            let caseType = case.Name |> case.DeclaringType.GetNestedType 
            baseType.AddSubType(1000 + case.Tag, caseType) |> ignore
            let caseTypeModel = model.[caseType]
            caseTypeModel.Add("item").UseConstructor <- false
        baseType.CompileInPlace()
    
    let registerSerializableDu<'TMessage> () = registerSerializableDuInModel<'TMessage> RuntimeTypeModel.Default
    
    registerSerializableDu ()
    

    to address the need for [] cruft. (I'm still pondering whether what mix of F# and protbuf-net improvements would best address that)

    A pretty important difference is lack of need for [] sprinklage (in addition to the ProtoInclude and ProtoMember ones).

    Code dump:

    module FunDomain.Tests.ProtobufNetSerialization
    
    open ProtoBuf
    open ProtoBuf.Meta
    
    open Swensen.Unquote
    open Xunit
    
    open System.IO
    open Microsoft.FSharp.Reflection
    
    []
    type MessageA = {
        [] X: string;
        [] Y: int option;
    }
    
    []
    []
    type MessageB = {
        [] A: string;
        [] B: string;
    }
    
    []
    type Message =
        | MessageA of MessageA
        | MessageB of MessageB
    
    let serialize msg =
        use ms = new MemoryStream()
        Serializer.SerializeWithLengthPrefix(ms, msg, PrefixStyle.Fixed32)
        ms.ToArray()
    
    let deserialize<'TMessage> bytes =
        use ms = new MemoryStream(buffer=bytes)
        Serializer.DeserializeWithLengthPrefix<'TMessage>(ms, PrefixStyle.Fixed32)
    
    let registerSerializableDuInModel<'TMessage> (model:RuntimeTypeModel) =
        let baseType = model.[typeof<'TMessage>]
        for case in typeof<'TMessage> |> FSharpType.GetUnionCases do
            let caseType = case.Name |> case.DeclaringType.GetNestedType 
            baseType.AddSubType(1000 + case.Tag, caseType) |> ignore
            let caseTypeModel = model.[caseType]
            caseTypeModel.Add("item").UseConstructor <- false
        baseType.CompileInPlace()
    
    let registerSerializableDu<'TMessage> () = registerSerializableDuInModel<'TMessage> RuntimeTypeModel.Default
    
    registerSerializableDu ()
    
    let [] ``MessageA roundtrips with null`` () =
        let msg = {X=null; Y=None}
        let result = serialize msg
        test <@ msg = deserialize result @>
    
    let [] ``MessageA roundtrips with Empty`` () =
        let msg = {X=""; Y=None}
        let result = serialize msg
        test <@ msg = deserialize result @>
    
    let [] ``MessageA roundtrips with Some`` () =
        let msg = {X="foo"; Y=Some 32}
        let result = serialize msg
        test <@ msg = deserialize result @>
    
    let [] ``MessageA roundtrips with None`` () =
        let msg = {X="foo"; Y=None}
        let result = serialize msg
        test <@ msg = deserialize result @>
    
    let [] ``MessageB roundtrips`` () =
        let msg = {A="bar"; B="baz"}
        let result = serialize msg
        test <@ msg = deserialize result @>
    
    let [] ``roundtrip pair``() =
        let msg1 = MessageA {X="foo"; Y=Some 32}
        let msg1' = msg1 |> serialize |> deserialize
        test <@ msg1' = msg1 @>
    
        let msg2 = MessageB {A="bar"; B="baz"}     
        let msg2' = msg2 |> serialize |> deserialize
        test <@ msg2' = msg2 @>
    
    let [] many() =
        for _ in 1..1000 do
            ``roundtrip pair``()      
    

提交回复
热议问题