XML 使用 C# 代码和 F# 模型进行反序列化

XML deserialization using C# code and F# models

我有将某些模型从 C# 迁移到 F# 的不寻常要求,老实说,我在 XML 反序列化方面遇到了一些困难。这是我到目前为止所拥有的。以下 XML + 反序列化 C# 代码 + F# 模型的配对非常有效:

<nameServers>
    <hostNames>
        <AddressWrapper>
            <Address>www.blah1.com</Address>
        </AddressWrapper>
        <AddressWrapper>
            <Address>www.blah2.com</Address>
        </AddressWrapper>
    </hostNames>
</nameServers>
[<CLIMutable>]
[<XmlTypeAttribute("AddressWrapper")>]
type AddressWrapper =
    { [<XmlElement("Address")>]
      Address: string }

[<CLIMutable>]
type WhoisRecordNameServersV2 =
    { [<XmlArray("hostNames")>]
      hostNames: AddressWrapper [] }

以下只是随笔,不要对代码附加任何意义:

var xmlResponse = await File.ReadAllTextAsync(xmlResponsePath);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xmlResponse));
using var xmlReader = new XmlTextReader(stream) {XmlResolver = null};
var serializer = new XmlSerializer(typeof(WhoisServiceExternal.WhoisRecordV2));
var record = serializer.Deserialize(xmlReader) as WhoisServiceExternal.WhoisRecordV2;

并且 record 确实已正确创建并填充了其属性。但是, XML 并不是我真正想要反序列化的。我真正想要的是:

<nameServers>
    <hostNames>
        <Address>ns1.google.com</Address>
        <Address>ns2.google.com</Address>
    </hostNames>
</nameServers>

我的问题是:如何调整我的 F# 模型,以便 XML 的第二部分正确反序列化?

我觉得问这样一个微不足道的问题很傻,但我觉得我已经尝试了太多模型属性的配置,不同的属性和名称,但似乎没有任何效果 - Address 字段的集合不是正确反序列化。我要么看到一个空的 hostNames 集合,要么看到空白的 Address 属性。

我肯定遗漏了一些明显的东西。


更新:

感谢您回答费奥多尔。以下是我未修剪的 XML 和工作模型:

[<CLIMutable; XmlType("Address")>]
type Address =
    { [<XmlText>]
      Address: string }

[<CLIMutable>]
type WhoisRecordNameServersV2 =
    { rawText: string
      [<XmlArray("hostNames")>]
      hostNames: Address [] }
<nameServers>
    <rawText>ns1.google.com
        ns3.google.com
        ns2.google.com
        ns4.google.com
    </rawText>
    <hostNames>
        <Address>ns1.google.com</Address>
        <Address>ns2.google.com</Address>
    </hostNames>
    <ips/>
</nameServers>

看来你这里的是:

  1. <nameServers>,其中
  2. <hostNames>,其中
  3. <Address>的数组,其中
  4. 纯文本

并且您想将其映射到:

  1. WhoisRecordNameServersV2,其中
  2. hostNames,其中
  3. 一个AddressWrapper的数组,其中
  4. Address: string

看到水货了吗?对我来说,这看起来像是幸运的一对一映射:

  1. <nameServers> 映射到 WhoisRecordNameServersV2
  2. <hostNames> 映射到 hostNames
  3. <Address> 映射到 AddressWrapper
  4. <Address> 内的纯文本映射到 AddressWrapper 内的 Address

现在您需要做的就是正确注释类型和字段:

  1. WhoisRecordNameServersV2映射到<nameServers>,所以需要XmlType("nameServers")
  2. hostNames 映射到 <hostNames> 并包含一个数组,所以它需要 XmlArray("hostNames")
  3. AddressWrapper映射到<Address>,所以需要XmlType("Address")
  4. AddressWrapper内的
  5. Address映射到<Address>内的纯文本,所以需要XmlText.

综合起来:

[<CLIMutable; XmlType("Address")>]
type AddressWrapper =
    { [<XmlText>] Address: string }

[<CLIMutable; XmlType("nameServers")>]
type WhoisRecordNameServersV2 =
    { [<XmlArray("hostNames")>] hostNames: AddressWrapper [] }