有没有办法让protobuf序列化/反序列化F#的歧视联盟?
我尝试使用protobuf序列化邮件。消息是F#记录和受歧视的联合。
序列化似乎对记录工作正常,但我不能让它与受歧视的工会合作。
在下面的代码中,testMessageA和testMessageB的测试是绿色的。测试testMessageDU为红色。
module ProtoBufSerialization
open FsUnit
open NUnit.Framework
open ProtoBuf
type MessageA = {
X: string;
Y: int;
}
type MessageB = {
A: string;
B: string;
}
type Message =
| MessageA of MessageA
| MessageB of MessageB
let serialize msg =
use ms = new System.IO.MemoryStream()
Serializer.SerializeWithLengthPrefix(ms, msg, PrefixStyle.Fixed32)
ms.ToArray()
let deserialize<'TMessage> bytes =
use ms = new System.IO.MemoryStream(buffer=bytes)
Serializer.DeserializeWithLengthPrefix<'TMessage>(ms, PrefixStyle.Fixed32)
[<Test>]
let testMessageA() =
let msg = {X="foo"; Y=32}
msg |> serialize |> deserialize<MessageA> |> should equal msg
[<Test>]
let testMessageB() =
let msg = {A="bar"; B="baz"}
msg |> serialize |> deserialize<MessageB> |> should equal msg
[<Test>]
let testMessageDU() =
let msg = MessageA {X="foo"; Y=32}
msg |> serialize |> deserialize<Message> |> should equal msg
我尝试在类型Message上添加ProtoInclude和KnownType等不同属性,在MessageA和MessageB类型上添加CLIMutable,但似乎没有任何帮助。
我不希望将我的DU映射到类以使序列化工作...
答案 0 :(得分:4)
我玩了非常有用的生成输出,看起来基本上一切正常 - 除了 Message.MessageA
子类型。这些非常接近工作 - 它们基本上与“自动元组”代码(匹配所有成员的构造函数)相同,除了自动元组当前不适用于子类型。 / p>
我认为应该可以调整代码以自动工作,通过扩展自动元组代码在这种情况下工作(我试图想到任何可能的不良副作用那,但我没有看到任何)。我没有具体的时间框架,因为我需要在多个项目和全职日间工作,家庭,志愿者工作和(等等)之间平衡时间。
在短期内,以下C#足以使其发挥作用,但我不认为这将是一个有吸引力的选择:
RuntimeTypeModel.Default[typeof(Message).GetNestedType("MessageA")]
.Add("item").UseConstructor = false;
RuntimeTypeModel.Default[typeof(Message).GetNestedType("MessageB")]
.Add("item").UseConstructor = false;
顺便说一下,这里的属性是无益的,应该避免:
| [<ProtoMember(1)>] MessageA of MessageA
| [<ProtoMember(2)>] MessageB of MessageB
如果他们做了什么,他们就会重复<ProtoInclude(n)>
的意图。如果更方便在那里指定它们,那可能会很有趣。但我发现真的有趣的是,F#编译器完全忽略了AttributeUsageAttribute
,[ProtoMember]
是:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
AllowMultiple = false, Inherited = true)]
public class ProtoMemberAttribute {...}
是的,F#编译器明确地将其(非法)粘贴在方法上:
[ProtoMember(1)]
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
public static ProtoBufTests.Message NewMessageA(ProtoBufTests.MessageA item)
顽皮的F#编译器!
答案 1 :(得分:3)
我使用protobuf-net
加注event sourcing DUs并非常感谢json.net v6's seamless support for DUs。
我最初希望优先使用protobuf-net的原因是:
我希望在我的邮件合同中能够抵御字段重命名(依赖于通过[<ProtoMember(n)>]
解决),这可以通过以下方式减轻:
EventXXXV2
旁边EventXxx
添加同一个DU 我找不到比以前更清洁的方式:
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<Message> ()
解决[<ProtoInclude(100, "ProtoBufTests+Message+MessageA")>]
残骸的需要。 (我还在思考F#和protbuf-net改进的混合是否能最好地解决这个问题)
一个非常重要的区别是[<ProtoContract; CLIMutable>]
喷洒不需要(除ProtoInclude
和ProtoMember
之外)。
代码转储:
module FunDomain.Tests.ProtobufNetSerialization
open ProtoBuf
open ProtoBuf.Meta
open Swensen.Unquote
open Xunit
open System.IO
open Microsoft.FSharp.Reflection
[<ProtoContract; CLIMutable>]
type MessageA = {
[<ProtoMember(1)>] X: string;
[<ProtoMember(2)>] Y: int option;
}
[<ProtoContract>]
[<CLIMutable>]
type MessageB = {
[<ProtoMember(1)>] A: string;
[<ProtoMember(2)>] B: string;
}
[<ProtoContract>]
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<Message> ()
let [<Fact>] ``MessageA roundtrips with null`` () =
let msg = {X=null; Y=None}
let result = serialize msg
test <@ msg = deserialize result @>
let [<Fact>] ``MessageA roundtrips with Empty`` () =
let msg = {X=""; Y=None}
let result = serialize msg
test <@ msg = deserialize result @>
let [<Fact>] ``MessageA roundtrips with Some`` () =
let msg = {X="foo"; Y=Some 32}
let result = serialize msg
test <@ msg = deserialize result @>
let [<Fact>] ``MessageA roundtrips with None`` () =
let msg = {X="foo"; Y=None}
let result = serialize msg
test <@ msg = deserialize result @>
let [<Fact>] ``MessageB roundtrips`` () =
let msg = {A="bar"; B="baz"}
let result = serialize msg
test <@ msg = deserialize result @>
let [<Fact>] ``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 [<Fact>] many() =
for _ in 1..1000 do
``roundtrip pair``()
答案 2 :(得分:0)
我最终做的是这样的事情
let typeModel = TypeModel.Create()
let resultType = typedefof<Result>
let resultNestedTypes = resultType.GetNestedTypes() |> Array.filter (fun x -> x.Name <> "Tags")
for nestedType in resultNestedTypes do
let model = typeModel.Add( nestedType, true )
model.UseConstructor <- false
nestedType.GetFields( BindingFlags.NonPublic ||| BindingFlags.Instance ||| BindingFlags.GetField ) |> Array.map (fun x -> x.Name ) |> Array.sort |> model.Add |> ignore
types.[ nestedType.Name ] <- nestedType
在我的情况下,types
是启动应用程序时构建的联合类型的字典。我需要在序列化数据之前将消息中的名称保存到以后才能加载它。
只要添加新字段,这将有效,因为每个字段都变为item1
。如果需要删除字段,我认为可以很容易地扩展以从字段名称中获取字段顺序号,如此
type Result =
| Success of Item1: string * Item3:bool
| Failure of string
然后在Item之后提取数字,或者提供最好的工作。有很多方法。