【问题标题】:What's the best way to parse YAML in F# on .NET Core?在 .NET Core 上的 F# 中解析 YAML 的最佳方法是什么?
【发布时间】:2018-03-23 15:57:08
【问题描述】:

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

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

使用库是可以的,但是那个库的接口越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 文件更重要,所以不管怎样……

【问题讨论】:

  • 我之前对 YamlDotNet 有过很好的体验,但我不知道它是否支持 .NET Core。对我来说,驱动因素是文档模型,将所有不同的节点类型封装在活动模式中非常简单,以便在 F# 中感觉更自在。
  • @scrwtp 有趣!在写这个问题之前,我尝试了 YamlDotNet(很短的时间),我很难弄清楚如何有效地使用它。也许您可以在答案中使用一些代码示例来概述您的方法?
  • 我无法再访问该代码库,所以我将从头开始。如果您使用 MCVE 发布具体问题,我会看看。

标签: parsing f# yaml


【解决方案1】:

为时已晚,但将来可能对其他人有用


找到支持.NET Standard 2.0https://fjoppe.github.io/Legivel/
教程https://fjoppe.github.io/Legivel/tutorial.html
作者https://github.com/fjoppe/Legivel

F# 中的 Yaml 到本机处理器,生成 F# 类型。

这个例子演示了使用这个库从 Yaml 到 Native 的转换:

#r "Legivel.Parser.dll"
#r "Legivel.Mapper.dll"
open Legivel.Serialization

type PlayEvent = {
  time   : string
  player : string
  action : string
}

//  example : http://www.yaml.org/spec/1.2/spec.html#id2760519
let yaml = "
---
time: 20:03:20
player: Sammy Sosa
action: strike (miss)
...
---
time: 20:03:47
player: Sammy Sosa
action: grand slam
..." 

然后

Deserialize<PlayEvent> yaml

结果:

[Succes {Data = {time = "20:03:20";
                 player = "Sammy Sosa";
                 action = "strike (miss)";};
         Warn = [];}; Succes {Data = {time = "20:03:47";
                                      player = "Sammy Sosa";
                                      action = "grand slam";};
                              Warn = [];}]

【讨论】:

    【解决方案2】:

    是的,在让 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
    

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-09-16
      • 1970-01-01
      • 2011-01-31
      • 2010-10-28
      • 2013-11-22
      • 2010-09-14
      • 1970-01-01
      相关资源
      最近更新 更多