我需要一个项目,其中 YAML 模式被定义为 JSON 模式,我用它来生成类(通过NJsonSchema
)。但是,实际数据以 YAML 形式出现。所以,我需要将 YAML 反序列化到类中。
我利用这种机制,如果一个类实现IDictionary<string, object
or 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
。在我的解决方案中,所有属性都需要为 property 定义一个别名。
然后,我找到具有与我从AddAndMap
调用中获得的密钥匹配的别名的属性。这会递归地发生,因此(将来)可以使用自定义类属性进行这项工作。Dictionary<object, object>
它作为值类型进行检查,因为YamlDotNet
在将某些内容反序列化为字典时,总是将子类反序列化为Dictionary<object, object>
.
全面的
如果您尝试反序列化的类只有平面(没有自定义类属性),则此解决方案有效并且可以简化。或者,它可以扩展为支持自定义类属性。该解决方案也可以制成其他类派生自的基本类型,并且您可以GetType()
使用 inDeserializableProperties
而不是typeof(Language)
. 这是一个过于冗长的解决方案,但在YamlDotNet
有一个更清洁/更简单/适当的解决方案之前,这是我在探索他们的源代码后能想到的最好的解决方案。
最后,请记住,由于您的类派生自IDictionary
,YamlDotNet
因此不会序列化您的类中的任何属性,即使它们具有YamlMember
属性。如果YamlDotNet
可以提供该功能,那么我最初就想到了一个更清洁的解决方案。唉,事实并非如此。