在.NET Core上的F#中解析YAML的最佳方法是什么?

Tom*_*han 6 f# parsing yaml

鉴于尚不支持类型提供程序,我需要一些其他方便的方法来解析F#中的YAML文件。但是,由于类型提供者是如此出色,因此我在互联网上寻找替代解决方案时很难找到其他任何东西。

考虑到类型提供程序不在表中,解析F#中的配置文件的最简单方法是什么?

使用一个库很好,但是该库的接口越是面向对象,那么在F#中使用它的便利性就越差。

我也不需要完整的“将任何Yaml反序列化为给定的类型/对象图”;类似xpath查询,但是对于YAML来说是完全可以的;我只是不想读取流并手动解析它。


这是我当前的尝试,由于区分类型的联合OneOfSeveral没有默认构造函数,因此在运行时失败。它需要一些特殊的处理并不令我感到惊讶,但是我不知道如何去做。

open System
open YamlDotNet.Serialization

[<CLIMutable>]
type SpecificThing = {
    foo : string
    bar : int
}

type OneOfSeveral = Thing of SpecificThing

[<CLIMutable>]
type Root = {
    option : OneOfSeveral
}


[<EntryPoint>]
let main argv =
    let yaml = @"---
    option:
      thing:
        foo: foobar
        bar: 17
    "

    let deserializer = DeserializerBuilder().Build()
    let config = deserializer.Deserialize<Root>(yaml)
    printfn "%A" config
    0
Run Code Online (Sandbox Code Playgroud)

我也不确定如何在YAML的类型联合中表示选择。我考虑了几种选择:

# omit the 'option' level completely
thing:
  foo: foobar
  bar: 17

# have a specific field to discriminate on
option:
  type: thing
  foo: foobar
  bar: 17
Run Code Online (Sandbox Code Playgroud)

最后,对我来说,为配置配置一个灵活的对象图比拥有一个漂亮的YAML文件更为重要,因此无论如何,...

scr*_*wtp 7

是的,当让 F# 类型与 C# 目标库一起工作时,DU 缺少无参数构造函数是一个常见的痛点。

看着YamlDotNet,似乎有定制的两种序列化/反序列化,IYamlTypeConverterIYamlConvertible接口。

我曾简要地研究过IYamlTypeConverter为所有联合类型编写通用程序,但遇到了无法将联合字段的序列化推迟到原始序列化程序的问题。话虽如此,您可以IYamlTypeConverters专门为您关心的类型实现。

另一个更轻量级的选择是使用无参数构造函数为联合创建一个包装类型,该构造函数将IYamlConvertible在您的配置类型中实现和公开它。

过去我采用了不同的方法 - 我使用的是 YamlDotNet 的表示模型部分,而不是序列化器/反序列化器接口。这是从 yaml 字符串打开流的方式:

open System.IO
open YamlDotNet.RepresentationModel

let read yaml = 
    use reader = new StringReader(yaml)
    let stream = YamlStream()
    stream.Load(reader)
    stream.Documents

let doc = read yaml

doc.[0].RootNode
Run Code Online (Sandbox Code Playgroud)

这为您提供了文档的通用树表示。然后我会有一个沿着这些线的活动模式来简化遍历这棵树的编写函数:

let (|Mapping|Scalar|Sequence|) (yamlNode: YamlNode) =  
    match yamlNode.NodeType with    
    | YamlNodeType.Mapping  -> 
        let node = yamlNode :?> YamlMappingNode
        let mapping = 
            node.Children 
            |> Seq.map (fun kvp -> 
                let keyNode = kvp.Key :?> YamlScalarNode
                keyNode.Value, kvp.Value) 
            |> Map.ofSeq            
        Mapping (node, mapping)
    | YamlNodeType.Scalar   -> 
        let node = yamlNode :?> YamlScalarNode
        Scalar (node, node.Value)
    | YamlNodeType.Sequence -> 
        let node = yamlNode :?> YamlSequenceNode
        Sequence (node, List.ofSeq node.Children)
    | YamlNodeType.Alias 
    | _ -> failwith "¯\_(?)_/¯"
Run Code Online (Sandbox Code Playgroud)

现在您可以针对该表示编写函数,就像这里的 XPath 想要的:

let rec go (path: string list) (yamlNode: YamlNode) =
    match path with
    | [] -> Some yamlNode
    | x::xs ->
        match yamlNode with
        | Mapping (n, mapping) ->  
            match mapping |> Map.tryFind x with
            | Some nested -> 
                go xs nested
            | None -> None
        | Sequence _
        | Scalar _ -> None

go ["option"; "thing"; "bar"] doc.[0].RootNode
Run Code Online (Sandbox Code Playgroud)