【问题标题】:Deserialize a YAML "Table" of data反序列化 YAML 数据“表”
【发布时间】:2015-06-17 14:23:52
【问题描述】:

我正在使用 yamldotnet 和 c# 来反序列化由第三方软件应用程序创建的文件。以下 YAML 文件示例在应用程序中均有效:

#File1
Groups:
  - Name: ATeam
    FirstName, LastName, Age, Height:
      - [Joe, Soap, 21, 184]
      - [Mary, Ryan, 20, 169]
      - [Alex, Dole, 24, 174]

#File2
Groups:
  - Name: ATeam
    FirstName, LastName, Height:
      - [Joe, Soap, 184]
      - [Mary, Ryan, 169]
      - [Alex, Dole, 174]

请注意,File2 没有任何 Age 列,但反序列化程序仍必须识别每行的第三个值是高度而不是年龄。这个数据应该代表一张人的桌子。以 File1 为例,Mary Ryan 20 岁,身高 169 厘米。反序列化器需要了解它拥有的列(对于 File2,它只有 FirstName、LastName 和 Height)并将数据相应地存储在正确的对象中:Mary Ryan 身高 169 厘米。

同样,程序文档指出列的顺序并不重要,因此下面的 File3 是一种同样有效的方式来表示 File2 中的数据,即使现在首先是高度:

#File3
Groups:
 - Name: ATeam
   Height, FirstName, LastName:
      - [184, Joe, Soap]
      - [169, Mary, Ryan]
      - [174, Alex, Dole]

我有几个问题:

  1. 这是标准 YAML 吗? - 我找不到任何关于使用 同一行上的多个键,后跟一个冒号和列表 表示数据表的值。
  2. 如何使用 yamldotnet 反序列化它?在那儿 我可以做些修改来帮助它吗?
  3. 如果我不能使用yamldotnet,我该怎么办?

【问题讨论】:

  • 这些都是有效的 YAML 文件(除了最后一个中的额外缩进空间,我将其作为错字删除)。

标签: c# yaml yamldotnet


【解决方案1】:

正如其他答案所述,这是有效的 YAML。但是,文档的结构是特定于应用程序的,并没有使用 YAML 的任何特殊功能来表达表格。

您可以使用 YamlDotNet 轻松解析此文档。但是你会遇到两个困难。第一个是,由于列的名称放在键内,您将需要使用一些自定义序列化代码来处理它们。第二个是您需要实现某种抽象,以便能够以表格方式访问数据。

我已经提出了一个概念证明来说明如何解析和读取数据。

首先,创建一个类型来保存 YAML 文档中的信息:

public class Document
{
    public List<Group> Groups { get; set; }
}

public class Group
{
    public string Name { get; set; }

    public IEnumerable<string> ColumnNames { get; set; }

    public IList<IList<object>> Rows { get; set; }
}

然后实现IYamlTypeConverter解析Group类型:

public class GroupYamlConverter : IYamlTypeConverter
{
    private readonly Deserializer deserializer;

    public GroupYamlConverter(Deserializer deserializer)
    {
        this.deserializer = deserializer;
    }

    public bool Accepts(Type type)
    {
        return type == typeof(Group);
    }

    public object ReadYaml(IParser parser, Type type)
    {
        var group = new Group();

        var reader = new EventReader(parser);
        do
        {
            var key = reader.Expect<Scalar>();
            if(key.Value == "Name")
            {
                group.Name = reader.Expect<Scalar>().Value;
            }
            else
            {
                group.ColumnNames = key.Value
                    .Split(',')
                    .Select(n => n.Trim())
                    .ToArray();

                group.Rows = deserializer.Deserialize<IList<IList<object>>>(reader);
            }
        } while(!reader.Accept<MappingEnd>());
        reader.Expect<MappingEnd>();

        return group;
    }

    public void WriteYaml(IEmitter emitter, object value, Type type)
    {
        throw new NotImplementedException("TODO");
    }
}

最后,将转换器注册到反序列化器中并反序列化文档:

var deserializer = new Deserializer();
deserializer.RegisterTypeConverter(new GroupYamlConverter(deserializer));

var document = deserializer.Deserialize<Document>(new StringReader(yaml));

You can test the fully working example here

这只是一个概念证明,但它应该作为您自己实施的指南。可以改进的地方包括:

  • 检查和处理无效文件。
  • 改进Group 类。也许让它不可变,并添加一个索引器。
  • 如果需要序列化支持,则实现 WriteYaml 方法。

【讨论】:

  • 感谢 Antoine 的全面回答。我想知道有没有一种方法可以基于结构而不是依赖名称来做到这一点。我的文档实际上要复杂得多,因为除了“名称”和其他几个嵌套列表之外,还有许多其他属性。例如,如果该行至少包含一个逗号和一个冒号,则将其作为列名读取,并期待随后的列表列表。如果我要使用您的代码作为模板,我会使用大量的“if(key.Value == "Name")”等。
  • 在这种情况下,反序列化为Dictionary&lt;string, object&gt; 可能更简单,根本不使用类型转换器。结果字典将包含StringList&lt;object&gt; 的值。之后,您需要检查字典并正确解释。
【解决方案2】:

所有这些都是有效的 YAML 文件。但是,您错误地将带有逗号的标量键解释为构成与该键关联的值序列中的“列”的描述在 YAML 中。

在文件 1 中,FirstName, LastName, Age, Height 是映射的单个字符串标量键,它是序列的第一个元素,它是顶层键 Group 的值。就像name 一样。您可以但不必在 YAML 中为整个标量加上引号。

YAML 中不存在字符串“Firstname”和“Joe”之间的关联,您可以在解释密钥的程序中进行关联(通过在", " 上拆分),就像您正在做的那样,但 YAML 对此一无所知。

因此,如果您想对此有所了解,则需要自己拆分字符串"FirstName, LastName, Age, Height",并使用某种机制然后使用“子键”来索引与键关联的序列。

如果有助于理解这一切,以下是第一个文件内容的 json 转储,您可以清楚地看到键的组成:

{"Groups": [{"FirstName, LastName, Age, Height": [["Joe", "Soap", 21,
   184], ["Mary", "Ryan", 20, 169], ["Alex", "Dole", 24, 174]], 
   "Name": "ATeam"}]}

我为此使用了基于 Python 的 ruamel.yaml 库(我是该库的作者),但您也可以使用在线转换器/检查器,例如 http://yaml-online-parser.appspot.com/

【讨论】:

    【解决方案3】:

    我来晚了,但我最近一直在思考同样的问题。

    正如其他人所指出的,最好将列名记录为值,而不是键,并且您还可以取消额外的 Name 字段:

    Groups:
      ATeam:
        Columns: [FirstName, LastName, Height]
        Rows:
          - [Joe, Soap, 184]
          - [Mary, Ryan, 169]
          - [Alex, Dole, 174]
    

    或者不那么明确:

    Groups:
      ATeam:
        - [FirstName, LastName, Height]
        - [Joe, Soap, 184]
        - [Mary, Ryan, 169]
        - [Alex, Dole, 174]
    

    这基本上是一个 YAML 格式的 CSV 文件;表格行显示为行。

    我认为从 YAML 结构的语义中更有意义的替代方法是,因为它将列名与值直接关联起来,是让表列显示为行:

    Groups:
      ATeam:
        FirstName: [Joe, Mary, Alex]
        LastName: [Soap, Ryan, Dole]
        Height: [184, 169, 174]
    

    这样,可以通过添加一行来添加一个额外的Age 列,而根本不改变其余部分。当然,添加额外的行会影响很多行。

    【讨论】:

      猜你喜欢
      • 2020-11-06
      • 1970-01-01
      • 2011-10-16
      • 2013-08-21
      • 2015-03-16
      • 2020-08-02
      • 2014-11-18
      • 2017-11-02
      • 2016-09-16
      相关资源
      最近更新 更多