反序列化 YAML "Table" 数据
Deserialize a YAML "Table" of data
我正在使用 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 列,但反序列化器仍必须识别每行的第三个值是身高而不是年龄。此数据应该代表 table 人。以 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]
我有很多问题:
- 这是标准的 YAML 吗? - 我找不到任何关于使用的信息
同一行上的一些键,后跟一个冒号和列表
代表 table 数据的值。
- 我如何使用 yamldotnet 反序列化它?在那里
我可以做一些修改来帮助它吗?
- 如果我不能使用 yamldotnet,我该怎么办?
所有这些都是有效的 YAML 文件。但是,您错误地将带有逗号的标量键解释为在与该键关联的值的序列中 "columns" 的 YAML 中构成描述 。
在文件 1 中,FirstName, LastName, Age, Height
是映射的单个字符串标量键,它是序列的第一个元素,是顶级键 Group
的值。就像 name
一样。您可以,但不必在 YAML 中,在整个标量周围加上引号。
您在字符串 "Firstname" 和 "Joe" 之间建立的关联在 YAML 中不存在,您可以在解释密钥的程序中建立该关联(通过将其拆分为 ", "
) 正如你似乎在做的那样,但 YAML 对此一无所知。
所以如果你想聪明一点,那么你需要自己拆分字符串 "FirstName, LastName, Age, Height"
并使用某种机制然后使用 "subkeys" 来索引与关键。
如果有助于理解所有这些,下面是第一个文件内容的 json 转储,您可以清楚地看到键的组成:
{"Groups": [{"FirstName, LastName, Age, Height": [["Joe", "Soap", 21,
184], ["Mary", "Ryan", 20, 169], ["Alex", "Dole", 24, 174]],
"Name": "ATeam"}]}
我为此使用了基于 Python 的 ruamel.yaml
库(我是作者),但您也可以使用在线 convertor/checker,例如 http://yaml-online-parser.appspot.com/
如其他答案所述,这是有效的 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
class。也许让它不可变,并添加一个索引器。
- 如果需要序列化支持,则实施
WriteYaml
方法。
我来晚了,但我最近一直在想同样的问题。
正如其他人所指出的,最好将列名记录为值,而不是键,您也可以取消额外的 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 文件; table 行显示为行。
替代方案,我认为从 YAML 结构的语义来看更有意义,因为它将列名直接与值相关联,是让 table 列显示为行:
Groups:
ATeam:
FirstName: [Joe, Mary, Alex]
LastName: [Soap, Ryan, Dole]
Height: [184, 169, 174]
这样,可以通过添加一行来添加额外的 Age
列,而无需更改其余部分。当然,多加一行会影响很多行。
我正在使用 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 列,但反序列化器仍必须识别每行的第三个值是身高而不是年龄。此数据应该代表 table 人。以 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]
我有很多问题:
- 这是标准的 YAML 吗? - 我找不到任何关于使用的信息 同一行上的一些键,后跟一个冒号和列表 代表 table 数据的值。
- 我如何使用 yamldotnet 反序列化它?在那里 我可以做一些修改来帮助它吗?
- 如果我不能使用 yamldotnet,我该怎么办?
所有这些都是有效的 YAML 文件。但是,您错误地将带有逗号的标量键解释为在与该键关联的值的序列中 "columns" 的 YAML 中构成描述 。
在文件 1 中,FirstName, LastName, Age, Height
是映射的单个字符串标量键,它是序列的第一个元素,是顶级键 Group
的值。就像 name
一样。您可以,但不必在 YAML 中,在整个标量周围加上引号。
您在字符串 "Firstname" 和 "Joe" 之间建立的关联在 YAML 中不存在,您可以在解释密钥的程序中建立该关联(通过将其拆分为 ", "
) 正如你似乎在做的那样,但 YAML 对此一无所知。
所以如果你想聪明一点,那么你需要自己拆分字符串 "FirstName, LastName, Age, Height"
并使用某种机制然后使用 "subkeys" 来索引与关键。
如果有助于理解所有这些,下面是第一个文件内容的 json 转储,您可以清楚地看到键的组成:
{"Groups": [{"FirstName, LastName, Age, Height": [["Joe", "Soap", 21,
184], ["Mary", "Ryan", 20, 169], ["Alex", "Dole", 24, 174]],
"Name": "ATeam"}]}
我为此使用了基于 Python 的 ruamel.yaml
库(我是作者),但您也可以使用在线 convertor/checker,例如 http://yaml-online-parser.appspot.com/
如其他答案所述,这是有效的 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
class。也许让它不可变,并添加一个索引器。 - 如果需要序列化支持,则实施
WriteYaml
方法。
我来晚了,但我最近一直在想同样的问题。
正如其他人所指出的,最好将列名记录为值,而不是键,您也可以取消额外的 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 文件; table 行显示为行。
替代方案,我认为从 YAML 结构的语义来看更有意义,因为它将列名直接与值相关联,是让 table 列显示为行:
Groups:
ATeam:
FirstName: [Joe, Mary, Alex]
LastName: [Soap, Ryan, Dole]
Height: [184, 169, 174]
这样,可以通过添加一行来添加额外的 Age
列,而无需更改其余部分。当然,多加一行会影响很多行。