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

标签 parsing f# yaml

鉴于该类型提供者 is not supported yet ,我需要一些其他方便的方法来解析 F# 中的 YAML 文件。但是,由于类型提供程序非常棒,当我在互联网上搜索替代解决方案时,我发现很难找到其他任何东西。

鉴于类型提供程序不在讨论范围内,在 F# 中解析配置文件的最简单方法是什么?

使用库很好,但是该库的接口(interface)越 OO,在 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

我也不确定我想如何在 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

最后,对我来说,拥有一个灵活的配置对象图比拥有一个漂亮的 YAML 文件更重要,所以不管怎样……

最佳答案

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

看 YamlDotNet,自定义序列化/反序列化似乎有两种方式,IYamlTypeConverterIYamlConvertible界面。

我已经简要介绍了如何编写通用 IYamlTypeConverter对于所有联合类型,但由于无法将联合字段的序列化推迟回原始序列化程序而陷入困境。话虽如此,您可以实现 IYamlTypeConverters专门针对您关心的类型。

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

过去我采用了一种不同的方法——我使用的是 YamlDotNet 的表示模型部分,而不是序列化器/反序列化器接口(interface)。这是您从 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

这为您提供了文档的通用树表示。然后我会有一个沿着这些线的事件模式来简化遍历这棵树的编写函数:
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 "¯\_(ツ)_/¯"

现在您可以针对该表示编写函数,就像这里的 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

关于parsing - 在 .NET Core 上用 F# 解析 YAML 的最佳方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46697298/

相关文章:

ruby-on-rails - File.open 和 YAML.load_file 显示同一文件的不同内容

arrays - Bash 从配置文件中解析数组

c++ - 被 YAML::NodeType::Undefined 与 yaml-cpp 混淆

f# - 如何为TcpListener编写自己的异步包装器?

mysql - database.yml 中的特殊字符作为密码

ansible - 使用另一个变量作为键YAML访问字典

c++ - 未通过空格分隔的Bison/Yacc解析器将跳过语法- “unexpected $end”

javascript - 同时使用多个正则表达式进行测试(用于句法分析)

F#:[<StructuredFormatDisplay>] 与覆盖 __.ToString()。 ¿怎么了?

f# - 我应该如何声明管道?