反序列化数据的 YAML“表”

本文关键字:YAML 数据 反序列化 | 更新日期: 2023-09-27 17:56:41

我正在使用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为例,玛丽·瑞安今年20岁,身高169厘米。反序列化程序需要理解它拥有的列(对于 File2,它只有名字、姓氏和高度),并将数据相应地存储在正确的对象中: Mary Ryan身高169cm。

同样,

程序文档指出列的顺序并不重要,因此下面的 File3 是表示 File2 中的数据的同样有效的方法,即使 Height 现在是第一位:

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

我有很多问题:

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

反序列化数据的 YAML“表”

正如其他答案所述,这是有效的 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));

您可以在此处测试完全工作的示例

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

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

所有这些都是有效的 YAML 文件。但是,您将使用逗号解释标量键误认为构成 YAML 中与该键关联的值序列中的"列"的描述。

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

您在字符串"Firstname"和"Joe"之间建立的关联在 YAML 中不存在,您可以在解释密钥的程序中建立该关联(通过在 ", " 上拆分它),就像您似乎正在做的那样,但 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/

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

正如其他人指出的那样,最好将列名记录为值而不是键,您也可以取消额外的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列,而无需更改其余部分。当然,添加额外的行会影响许多行。