我正在尝试使用YamlDotNet解析此YAML文档:
title: Document with dynamic properties
/a:
description: something
/b:
description: other something
进入这个对象:
public class SimpleDoc
{
[YamlMember(Alias = "title")]
public string Title { get; set;}
public Dictionary<string, Path> Paths { get; set; }
}
public class Path
{
[YamlMember(Alias = "description")]
public string Description {get;set;}
}
我希望/ a / b或任何其他不匹配的属性最终在Paths字典中。如何配置YamlDotNet以支持此方案?
反序列化设置为:
// file paths is the path to the specified doc :)
var deserializer = new Deserializer();
var doc = deserializer.Deserialize<SimpleDoc>(File.OpenText(filePath));
然而,由于SimpleDoc上没有属性 / a ,它失败了。如果我将构造函数中的 ignoreUnmatched 设置为true,则会按预期忽略它。
答案 0 :(得分:0)
对于一个将YAML模式定义为JSON模式的项目,我需要它,我曾使用它来生成类(通过NJsonSchema
)。但是,实际数据为YAML。因此,我需要将YAML反序列化到这些类中。
我利用这种机制,如果一个类实现了IDictionary<string, object
或IDictionary<object, object>
,则YamlDotNet会将来自该节点的所有键/值放入字典中。如果我是从Dictionary<string, object>
派生该类的,那么我将无权修改将值添加到该字典的方式,因此我被迫实现该接口。
internal partial class Language : IDictionary<string, object>
{
[YamlMember(Alias = "namespace")]
public string Namespace { get; set; }
[YamlMember(Alias = "discriminatorValue")]
public string DiscriminatorValue { get; set; }
[YamlMember(Alias = "uid")]
public string Uid { get; set; }
[YamlMember(Alias = "internal")]
public bool Internal { get; set; }
[YamlIgnore]
public IDictionary<string, object> AdditionalProperties = new Dictionary<string, object>();
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
private static readonly Dictionary<string, PropertyInfo> DeserializableProperties = typeof(Language).GetDeserializableProperties();
// Workaround for mapping properties from the dictionary entries
private void AddAndMap(string key, object value)
{
_dictionary.Add(key, value);
if (DeserializableProperties.ContainsKey(key))
{
var propInfo = DeserializableProperties[key];
propInfo.SetValue(this, propInfo.DeserializeDictionary(value));
return;
}
AdditionalProperties.Add(key, value);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(KeyValuePair<string, object> item) => AddAndMap(item.Key, item.Value);
public void Clear() => _dictionary.Clear();
public bool Contains(KeyValuePair<string, object> item) => _dictionary.ContainsKey(item.Key);
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
foreach (var item in _dictionary)
{
array[arrayIndex++] = item;
}
}
public bool Remove(KeyValuePair<string, object> item) => _dictionary.Remove(item.Key);
public int Count => _dictionary.Count;
public bool IsReadOnly => false;
public void Add(string key, object value) => AddAndMap(key, value);
public bool ContainsKey(string key) => _dictionary.ContainsKey(key);
public bool Remove(string key) => _dictionary.Remove(key);
public bool TryGetValue(string key, out object value) => _dictionary.TryGetValue(key, out value);
public object this[string key]
{
get => _dictionary[key];
set => AddAndMap(key, value);
}
public ICollection<string> Keys => _dictionary.Keys;
public ICollection<object> Values => _dictionary.Values;
}
在此示例中,Language
是我要反序列化的类。作为生成的类的一部分,我对该类具有其他属性(通过JSON模式生成器)。由于它们是局部的,因此我可以实现此解决方法。
基本上,我创建了AddAndMap
并将其应用于添加字典条目的界面中的任何位置。我还创建了AdditionalProperties
字典来保存我们的值。请记住,我们需要IDictionary
中的所有值才能正确地序列化此类。
接下来,我总是将条目添加到支持_dictionary
,然后确定我是否具有向其提供值的属性。如果我没有该值应映射到的属性,则将其放入AdditionalProperties
字典中。
要确定我是否有可用的反序列化属性,我必须编写一些扩展方法。
public static Dictionary<string, PropertyInfo> GetDeserializableProperties(this Type type) => type.GetProperties()
.Select(p => new KeyValuePair<string, PropertyInfo>(p.GetCustomAttributes<YamlMemberAttribute>(true).Select(yma => yma.Alias).FirstOrDefault(), p))
.Where(pa => !pa.Key.IsNullOrEmpty()).ToDictionary(pa => pa.Key, pa => pa.Value);
// Only allows deserialization of properties that are primitives or type Dictionary<object, object>. Does not support properties that are custom classes.
public static object DeserializeDictionary(this PropertyInfo info, object value)
{
if (!(value is Dictionary<object, object>)) return TypeConverter.ChangeType(value, info.PropertyType);
var type = info.PropertyType;
var properties = type.GetDeserializableProperties();
var property = Activator.CreateInstance(type);
var matchedProperties = ((Dictionary<object, object>)value).Where(e => properties.ContainsKey(e.Key.ToString()));
foreach (var (propKey, propValue) in matchedProperties)
{
var innerInfo = properties[propKey.ToString()];
innerInfo.SetValue(property, innerInfo.DeserializeDictionary(propValue));
}
return property;
}
要注意的主要事情是,这仅适用于类的原语或Dictionary<object, object>
属性。原因是因为设置属性时,它使用TypeConverter.ChangeType
返回适当的对象。如果您有一种方法来解释如何创建自定义类,则只需用该解决方案替换TypeConverter.ChangeType
调用即可。
要获取类的可反序列化的属性,我使用反射来获取该类的属性的YamlMemberAttribute
。然后,我将该属性的PropertyInfo
放入字典中,其中的键是Alias
中YamlMemberAttribute
的值。在我的解决方案中,所有属性都需要为该属性定义别名。
然后,我找到一个属性,该属性的别名与从AddAndMap
调用中获取的密钥相匹配。递归发生这种情况,以便(将来)可以使用自定义类属性进行此项工作。它检查Dictionary<object, object>
作为值类型,因为YamlDotNet
在将某些内容反序列化为字典时,总是会将子类反序列化为Dictionary<object, object>
。
如果您仅尝试反序列化类的平面(无自定义类属性),则此解决方案有效并且可以简化。或者,可以扩展它以支持自定义类属性。也可以将解决方案设置为您的其他类派生的基本类型,并且您将在GetType()
中使用DeserializableProperties
而不是typeof(Language)
。这是一个过于冗长的解决方案,但是直到YamlDotNet
拥有一个更简洁/更简单/正确的解决方案之后,这才是我探索其源代码后能想到的最好的解决方案。
最后,请记住,由于您的类是IDictionary
的派生类,因此YamlDotNet
不会序列化您类中的任何属性,即使它们具有{{1} }属性。如果YamlMember
将提供该功能,则本来我会想到一个更干净的解决方案。 las,事实并非如此。