首页 > 解决方案 > 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 。

标签: c#json.netjson-deserialization

解决方案


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"PrivateFieldAttributeTestIDictionary<string, TValue>TValue

  • 您可能希望静态缓存合同解析器以获得最佳性能

  • 转换器在反序列化时不会尝试删除私有属性,因为您的问题不需要这样做。

  • 您的Test类在序列化时包含NotPrivate 两次的值:一次作为属性,一次作为字典属性的成员:

    {
      "Data": {
        "SomeAdditionalKey": "Some additional value",
        "NotPrivate": "public value"
      },
      "NotPrivate": "public value"
    }
    

    您可能希望将其从一个位置或另一个位置排除。

  • 您写道,我无法从 JsonProperty 中获取字典。

    那是对的。合约解析器定义了如何将 .Net类型映射到 JSON。因此,特定实例在合同创建期间不可用。这就是为什么需要应用自定义转换器的原因,因为在序列化和反序列化期间将特定实例传递到转换器中。

演示小提琴在这里


推荐阅读