【问题标题】:F# Serialization of Record types记录类型的 F# 序列化
【发布时间】:2011-05-01 09:36:01
【问题描述】:

我知道如何在 F# 中使用可变对象进行序列化,但有没有办法使用 XmlSerializer 或 DataContractSerializer 使用记录类型进行序列化/反序列化?看起来有一种方法可以使用 KnownType 属性对有区别的联合执行此操作,但我正在寻找一种在没有默认构造函数的情况下使用非可变记录的方法...

【问题讨论】:

  • 我最终只使用了 xml 解析,这在我的情况下也是如此,我尝试的方法无法生成干净的 xml,即使 Tomas 建议使用 System.Xml.Serialization 的属性,我的怀疑是它不会起作用,但我还是决定尝试但他们没有,我相信因为数据合同的命名空间忽略了这些。我的要求是在本练习中使用非可变记录,但如果您的目标只是 xml 序列化,那么它与在任何其他 .net 语言中使用它没有什么不同,只需创建类、装饰它们并使用 XmlSerializer,它就可以工作。

标签: serialization f# xml-serialization


【解决方案1】:

Jomo Fisher 的示例 code for reading data from Freebase 使用 DataContractJsonSerializer 将数据加载到不可变的 F# 记录中。他使用的记录声明如下所示:

[<DataContract>]
type Result<'TResult> = { // '
    [<field: DataMember(Name="code") >]
    Code:string
    [<field: DataMember(Name="result") >]
    Result:'TResult // '
    [<field: DataMember(Name="message") >]
    Message:string }

这里的关键点是 DataMember 属性附加到实际用于存储数据的底层字段,而不是 F# 编译器生成的只读属性(使用 field: 修饰符属性)。

我不能 100% 确定这是否适用于其他类型的序列化(可能不会),但它可能是一个有用的指针……

编辑我不确定我是否在这里遗漏了什么,但以下基本示例对我来说很好:

module Demo

#r "System.Runtime.Serialization.dll"

open System.IO  
open System.Text  
open System.Xml 
open System.Runtime.Serialization
 
type Test = 
  { Result : string[]
    Title : string }
 
do
  let sb = new StringBuilder()
  let value = { Result = [| "Hello"; "World" |]; Title = "Hacking" }
  let xmlSerializer = DataContractSerializer(typeof<Test>); 
  xmlSerializer.WriteObject(new XmlTextWriter(new StringWriter(sb)), value)
  let sr = sb.ToString()
  printfn "%A" sr

  let xmlSerializer = DataContractSerializer(typeof<Test>); 
  let reader = new XmlTextReader(new StringReader(sr))
  let obj = xmlSerializer.ReadObject(reader) :?> Test
  printfn "Reading: %A" obj

EDIT 2如果您想生成更清晰的 XML,那么您可以添加如下属性:

[<XmlRoot("test")>] 
type Test = 
  { [<XmlArrayAttribute("results")>] 
    [<XmlArrayItem(typeof<string>, ElementName = "string")>] 
    Result : string[]
    [<XmlArrayAttribute("title")>] 
    Title : string }

【讨论】:

  • 这是一个开始,但是正在生成的 xml 不是“干净的”,我的意思是标签就像 "FSI_0132.Test" 而不是 Test 和 Title_x0040_ 而不是 title 。我看起来也无法在具有 [] 等属性的类型上设置 xml 标签。换句话说,xml 看起来是中间的,不是我可以真正使用的东西。顺便说一句,Tomas,我开始阅读你的 Real-World Functional Programming 这本书,到目前为止它看起来非常好。
  • 命名空间“FSI_0132”由 F# Interactive 生成​​ - 在编译后的代码中,它将替换为您的实际命名空间。不过,不确定属性名称。
  • @Joel:是的,我明白这一点,但我的目标是拥有没有任何命名空间信息的干净 xml,就像你尝试在 .net 中使用 XmlSerializer 类反序列化一样
  • @Alex:我尝试添加属性来控制生成的 XML,它也可以正常工作 - 请参阅第二个编辑。 (不过这次需要将属性添加到生成的属性中)
  • Tomas,我不是 100% 确定原因,但我的输出如下所示:w3.org/2001/XMLSchema-instance" xmlns="schemas.datacontract.org/2004/07"> schemas.microsoft.com/2003/10/Serialization/Arrays"> HelloWorldHacking请注意,这些属性似乎被忽略了
【解决方案2】:

从 F# 3.0 开始,现在通过将 CliMutableAttribute 应用于类型来支持记录类型的序列化。示例:

[<CLIMutable>] 
type MyRecord = { Name : string; Age : int }

此示例取自 http://blogs.msdn.com/b/fsharpteam/archive/2012/07/19/more-about-fsharp-3.0-language-features.aspx,其中包括对该功能和 F# 3.0 中其他三个新功能的讨论:三重引号字符串、自动属性和未使用的变量警告。

【讨论】:

    【解决方案3】:

    它不使用 XmlSerializer 或 DataContractSerializer,而是 Json.NET 6.0 includes nice F# support.

    看起来像这样:

    type TestTarget = 
        { a: string
          b: int }
    
    [<TestFixture>]
    type JsonTests() = 
        [<Test>]
        member x.``can serialize``() = 
            let objectUnderTest = { TestTarget.a = "isa"; b = 9 }
            let jsonResult: string = Newtonsoft.Json.JsonConvert.SerializeObject(objectUnderTest)
            printfn "json is:\n%s" jsonResult
            let xmlResult = Newtonsoft.Json.JsonConvert.DeserializeXmlNode(jsonResult, "root")
            printfn "xml is:\n%s" (xmlResult.OuterXml)
    
            let jsonRoundtrip = Newtonsoft.Json.JsonConvert.DeserializeObject<TestTarget>(jsonResult)
            printfn "json roundtrip: %A" jsonRoundtrip
    
            let xmlAsJson = Newtonsoft.Json.JsonConvert.SerializeXmlNode(xmlResult, Newtonsoft.Json.Formatting.Indented, true)
            printfn "object -> json -> xml -> json:\n%A" xmlAsJson
            let xmlRoundtrip = Newtonsoft.Json.JsonConvert.DeserializeObject<TestTarget>(xmlAsJson)
            printfn "xml roundtrip:\n%A" xmlRoundtrip
    
            Assert.That(true, Is.False)
            ()
    
    json is:
    {"a":"isa","b":9}
    xml is:
    <root><a>isa</a><b>9</b></root>
    json roundtrip: {a = "isa";
     b = 9;}
    object -> json -> xml -> json:
    "{
      "a": "isa",
      "b": "9"
    }"
    xml roundtrip:
    {a = "isa";
     b = 9;}
    

    【讨论】:

    • 展示了一种无需[&lt;CLIMutable&gt;] attr 就可以使用记录类型的方法(只需升级您的 newtonsoft)
    • 我发现这不尊重成员名称的大小写敏感性(它们总是被转换为小写)。
    【解决方案4】:

    您可以在类的属性上使用这一系列注释来格式化 XML:

    [XmlRoot("root")]
    [XmlElement("some-element")]
    [XmlAttribute("some-attribute")]
    [XmlArrayAttribute("collections")]
    [XmlArrayItem(typeof(SomeClass), ElementName = "item")]
    

    我在我的 c# 类上使用属性,但在 F# 中反序列化(c# 类是 ina 引用的库)。

    在 f# 中:

    use file = new FileStream(filePath, FileMode.Open)
    let serializer= XmlSerializer(typeof<SomeClass>)
    let docs = serializer.Deserialize file :?> SomeClass
    

    【讨论】:

    • 这很酷,但要求是使用非可变类型,因此这种方法不起作用,因为该类将具有可变字段
    猜你喜欢
    • 2014-05-12
    • 1970-01-01
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多