问题描述
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); } }
}
我想在序列化过程中从数据属性中删除特定的键 ,在我的情况下,字典中的键为“名称”,因为它被标记为私有。
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中获取字典。
解决方法
Json.NET没有内置功能来在序列化时从字典中过滤键。因此,您将需要创建一个进行必要过滤的custom JsonConverter
。然后,由于您无法更改课程,因此需要使用custom contract resolver应用转换器。
首先,自定义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,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,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 { }
最后,使用如下:
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>
的成员时,我将其应用于所有名为"Data"
的{{1}}属性。您可以将其限制为PrivateFieldAttribute
,或使用任何其他适合您的逻辑,只要对于任何类型Test
的转换器仅应用于类型为IDictionary<string,TValue>
的属性。 / p> -
您可能想statically cache the contract resolver for best performance。
-
转换器在反序列化时不会尝试删除私有属性,因为您的问题不需要这样做。
-
您的
TValue
类在序列化时包含Test
两次的值:一次作为属性,一次作为字典属性的成员:NotPrivate
您可能希望将其从一个位置或另一个位置排除。
-
您写道,我无法从JsonProperty中获取字典。
是正确的。合同解析器定义了如何将.Net 类型映射到JSON。因此,在合同创建期间特定实例不可用。这就是为什么必须应用自定义转换器的原因,因为在序列化和反序列化期间会将特定实例传递到转换器中。
演示小提琴here。
,public class IgnorePrivatePropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type,MemberSerialization memberSerialization)
{
IList<JsonProperty> properties = base.CreateProperties(type,memberSerialization);
if (!(type.Name.Equals(typeof(Test).Name)))
{
return properties;
}
var dataProperty = properties.FirstOrDefault(prop => prop.PropertyName.Equals("Data"));
if (dataProperty == null)
return properties;
var privatePropertiesNames = properties.Where(prop => prop.AttributeProvider.GetAttributes(false).OfType<PrivateFieldAttribute>().Any())
.Select(privateProperty => privateProperty.PropertyName);
dataProperty.ValueProvider = new JsonPropertyExtraDataValueProvider(privatePropertiesNames);
return properties;
}
}
public class JsonPropertyExtraDataValueProvider : IValueProvider
{
private IEnumerable<string> _privateKeys;
public JsonPropertyExtraDataValueProvider(IEnumerable<string> privateKeys)
{
_privateKeys = privateKeys;
}
public void SetValue(object target,object value)
{
throw new NotImplementedException();
}
public object GetValue(object target)
{
Dictionary<string,string> value = ((Test)target).ExtraData.ToDictionary(pr => pr.Key,pr => pr.Value);
foreach (var privateKey in _privateKeys)
value.Remove(privateKey);
return value;
}
}