c# - Newtonsoft.Json DefaultContractResolver 从字典中删除键
问题描述
我有以下课程(我不想更改课程来解决问题..):
public class Test
{
public Dictionary<string, string> Data;
[PrivateField]
[JsonIgnore]
public string Name { get { return Data["Name"]; } set { Data.Add("Name", value); } }
public string NotPrivate { get { return Data["NotPrivate"]; } set { Data.Add("NotPrivate", value); } }
}
我想在序列化期间从 Data 属性中删除特定键,在我的情况下,字典中的键是“名称”,因为它被标记为私有。
var test = new Test();
var settings = new JsonSerializerSettings();
settings.ContractResolver = new IgnorePrivatePropertiesContractResolver();
var places = JsonConvert.SerializeObject(test, settings);
public class IgnorePrivatePropertiesContractResolver : DefaultContractResolver
在 IgnorePrivatePropertiesContractResolver 我尝试过:
override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
但我无法从 JsonProperty 中获取字典。
我也试过:
JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
但我无法从 MemberInfo 中获取 Dictionary 。
解决方案
Json.NET 没有在序列化时从字典中过滤键的内置功能。因此,您将需要创建一个自定义JsonConverter
来执行必要的过滤。然后,由于您无法更改您的课程,您将需要使用自定义合同解析器来应用转换器。
首先,自定义JsonConverter
:
public class KeyFilteringDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
{
readonly HashSet<TKey> toSkip;
public KeyFilteringDictionaryConverter(IEnumerable<TKey> toSkip) => this.toSkip = toSkip?.ToHashSet() ?? throw new ArgumentNullException(nameof(toSkip));
public override void WriteJson(JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializer serializer) => serializer.Serialize(writer, new KeyFilteringDictionarySurrogate<TKey, TValue>(value, toSkip));
public override bool CanRead => false;
public override IDictionary<TKey, TValue> ReadJson(JsonReader reader, Type objectType, IDictionary<TKey, TValue> existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException();
}
public class KeyFilteringDictionarySurrogate<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
readonly IDictionary<TKey, TValue> dictionary;
readonly HashSet<TKey> toSkip;
public KeyFilteringDictionarySurrogate(IDictionary<TKey, TValue> dictionary, IEnumerable<TKey> toSkip) : this(dictionary, toSkip ?.ToHashSet()) { }
public KeyFilteringDictionarySurrogate(IDictionary<TKey, TValue> dictionary, HashSet<TKey> toSkip)
{
this.dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
this.toSkip = toSkip ?? throw new ArgumentNullException(nameof(toSkip));
}
public bool ContainsKey(TKey key) => !toSkip.Contains(key) && dictionary.ContainsKey(key);
public bool TryGetValue(TKey key, out TValue value)
{
if (toSkip.Contains(key))
{
value = default(TValue);
return false;
}
return dictionary.TryGetValue(key, out value);
}
public TValue this[TKey key] => toSkip.Contains(key) ? throw new KeyNotFoundException() : dictionary[key];
public IEnumerable<TKey> Keys => this.Select(p => p.Key);
public IEnumerable<TValue> Values => this.Select(p => p.Value);
public int Count => this.Count(); // Could be made faster?
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => dictionary.Where(p => !toSkip.Contains(p.Key)).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
接下来,自定义ContractResolver
. 应用自定义转换器最简单的地方是在CreateProperties()
创建所有属性信息之后。覆盖CreateObjectContract()
也可以。
public class IgnorePrivatePropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var jsonProperties = base.CreateProperties(type, memberSerialization);
// Apply to all string-keyed dictionary properties named "Data" that do not already have a converter
foreach (var dataProperty in jsonProperties.Where(p => p.PropertyName == "Data" && p.Converter == null))
{
var keyValuePairTypes = dataProperty.PropertyType.GetDictionaryKeyValueTypes().ToList();
if (keyValuePairTypes.Count == 1 && keyValuePairTypes[0][0] == typeof(string))
{
// Filter all properties with PrivateFieldAttribute applied
var ignoreProperties = jsonProperties.Where(p => p.AttributeProvider.GetAttributes(typeof(PrivateFieldAttribute), true).Any()).Select(p => p.PropertyName).ToHashSet();
if (ignoreProperties.Count > 0)
{
dataProperty.Converter = (JsonConverter)Activator.CreateInstance(typeof(KeyFilteringDictionaryConverter<,>).MakeGenericType(keyValuePairTypes[0]), new object [] { ignoreProperties });
}
}
}
return jsonProperties;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
=> (type ?? throw new ArgumentNullException()).IsInterface ? new[] { type }.Concat(type.GetInterfaces()) : type.GetInterfaces();
public static IEnumerable<Type []> GetDictionaryKeyValueTypes(this Type type)
=> type.GetInterfacesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>)).Select(t => t.GetGenericArguments());
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple=false)]
public class PrivateFieldAttribute : System.Attribute { }
最后,使用 eg 如下:
var test = new Test
{
Data = new Dictionary<string, string>
{
{"SomeAdditionalKey", "Some additional value"},
},
Name = "private value",
NotPrivate = "public value",
};
var resolver = new IgnorePrivatePropertiesContractResolver();
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(test, Formatting.Indented, settings);
笔记:
这个解决方案实际上并没有从字典中删除私钥,它只是跳过序列化它。通常,序列化实际上并不修改被序列化的对象。如果您确实需要在序列化期间实际修改字典,您的合同解析器可以应用自定义
OnSerializing()
方法来执行此操作,而不是应用转换器。您的问题没有具体说明如何确定
KeyFilteringDictionaryConverter
应该应用的属性。当同一个类中还有一个成员时,我将它应用于所有Dictionary<string, TValue>
命名的属性。您可以将其限制为 just ,或使用适合您需要的任何其他逻辑,只要转换器仅应用于任何 type的 type 属性即可。"Data"
PrivateFieldAttribute
Test
IDictionary<string, TValue>
TValue
您可能希望静态缓存合同解析器以获得最佳性能。
转换器在反序列化时不会尝试删除私有属性,因为您的问题不需要这样做。
您的
Test
类在序列化时包含NotPrivate
两次的值:一次作为属性,一次作为字典属性的成员:{ "Data": { "SomeAdditionalKey": "Some additional value", "NotPrivate": "public value" }, "NotPrivate": "public value" }
您可能希望将其从一个位置或另一个位置排除。
您写道,我无法从 JsonProperty 中获取字典。
那是对的。合约解析器定义了如何将 .Net类型映射到 JSON。因此,特定实例在合同创建期间不可用。这就是为什么需要应用自定义转换器的原因,因为在序列化和反序列化期间将特定实例传递到转换器中。
演示小提琴在这里。