简体   繁体   English

仅将复杂类型字典中的某些记录序列化为 JSON

[英]Serialize to JSON only some records from a dictionary in a complex type

I have the following contract:我有以下合同:

class Information
{
    public string SensitiveInformation { get; set; }
    public string NotSensitiveInformation { get; set; }

    public IDictionary<string, string> PartialSensitiveInformation { get; set; }
}

My goal is to serialize the class, but I need to ommit some sensitive information.我的目标是序列化类,但我需要省略一些敏感信息。 for this I've created a contract resolver:为此,我创建了一个合同解析器:

class IgnorePropertiesContractResolver : DefaultContractResolver
{
    private readonly HashSet<string> propertyNamesToIgnore;

    public IgnorePropertiesContractResolver(HashSet<string> propertyNamesToIgnore)
    {
        this.propertyNamesToIgnore = propertyNamesToIgnore;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty jsonProperty = base.CreateProperty(member, memberSerialization);
        if (this.propertyNamesToIgnore.Contains(jsonProperty.PropertyName))
        {
            jsonProperty.ShouldSerialize = x => false;
        }

        return jsonProperty;
    }
}

and running the code:并运行代码:

        IgnorePropertiesContractResolver resolver = new IgnorePropertiesContractResolver(new HashSet<string> {"SensitiveInformation" });

        Information info = new Information();
        info.SensitiveInformation = "sensitive data";
        info.NotSensitiveInformation = "not sensitive data";
        info.PartialSensitiveInformation = new Dictionary<string, string>();
        info.PartialSensitiveInformation["secret_data"] = "secret data";
        info.PartialSensitiveInformation["not_secret_data"] = "not secret data";

        var data = JsonConvert.SerializeObject(info, new JsonSerializerSettings { ContractResolver = resolver });

Returns this data: {"NotSensitiveInformation":"not sensitive data","PartialSensitiveInformation":{ "secret_data":"secret data" ,"not_secret_data":"not secret data"}}返回此数据: {"NotSensitiveInformation":"非敏感数据","PartialSensitiveInformation":{ "secret_data":"secret data" ,"not_secret_data":"not secret data"}}

Hor to change my contract resolver so I can ommit fom the serialization certain keys from the dictionary PartialSensitiveInformation ? Hor 更改我的合同解析器,以便我可以从字典PartialSensitiveInformation 中省略序列化某些键? I don't want to serialize the key "secret_data".我不想序列化密钥“secret_data”。

Please note that I have the contract in a nuget so adding attribute is not possible in this case.请注意,我在 nuget 中有合同,因此在这种情况下无法添加属性。

I'm using .net franework 4.7.2.我正在使用 .net franework 4.7.2。

You can implement a custom value provider (using IValueProvider ) to handle this.您可以实现自定义值提供程序(使用IValueProvider )来处理此问题。 The value provider can take in the list of sensitive property names, and transform the dictionary to exclude the sensitive properties:值提供者可以接受敏感属性名称列表,并转换字典以排除敏感属性:

public class PartialSensitiveInformationValueProvider : IValueProvider
{
    private readonly HashSet<string> _propertyNamesToIgnore;

    public PartialSensitiveInformationValueProvider(HashSet<string> propertyNamesToIgnore)
    {
        _propertyNamesToIgnore = propertyNamesToIgnore;
    }

    public object GetValue(object target)
    {
        return ((Information)target).PartialSensitiveInformation
            .Where(x => !_propertyNamesToIgnore.Contains(x.Key))
            .ToDictionary(k => k.Key, v => v.Value);
    }

    public void SetValue(object target, object value)
    {
        throw new NotImplementedException();
    }
}

You can then use this value provider in the contract resolver:然后,您可以在合同解析器中使用此值提供程序:

public class IgnorePropertiesContractResolver : DefaultContractResolver
{
    private readonly HashSet<string> propertyNamesToIgnore;

    public IgnorePropertiesContractResolver(HashSet<string> propertyNamesToIgnore)
    {
        this.propertyNamesToIgnore = propertyNamesToIgnore;
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty jsonProperty = base.CreateProperty(member, memberSerialization);
        if (this.propertyNamesToIgnore.Contains(jsonProperty.PropertyName))
        {
            jsonProperty.ShouldSerialize = x => false;
        }

        if (jsonProperty.PropertyName == nameof(Information.PartialSensitiveInformation))
        {
            jsonProperty.ValueProvider = new PartialSensitiveInformationValueProvider(this.propertyNamesToIgnore);
        }

        return jsonProperty;
    }
}

Using the example you have (including "secret_data" in the propertyNamesToIgnore ), the results are now:使用(包括在“secret_data”你有例子propertyNamesToIgnore ),现在的结果:

{"NotSensitiveInformation":"not sensitive data","PartialSensitiveInformation":{"not_secret_data":"not secret data"}}

My answer is a bit more involved than @devNull 's, the difference being it's a generic approach:我的回答比@devNull的回答要复杂一些,不同之处在于它是一种通用方法:

It allows you to target specific members using an expression, eg s => s.Inner.Dictionary .它允许您使用表达式来定位特定成员,例如s => s.Inner.Dictionary

Sample data:样本数据:

public class MyData
{
    public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string>();

    public MyDataInner Inner { get; set; } = new MyDataInner();
}

public class MyDataInner
{
    public Dictionary<string, string> Dictionary { get; set; } = new Dictionary<string, string>();
}

Newtonsoft related classes, contract, converter, resolver: Newtonsoft 相关类、合约、转换器、解析器:

public class MyJsonDictionaryContract : JsonDictionaryContract
{
    public MyJsonDictionaryContract(Type underlyingType, (string, string[])[] filters) : base(underlyingType)
    {
        Converter = new MyJsonConverter(filters);
    }
}

internal class MyJsonConverter : JsonConverter<IDictionary<string, string>>
{
    public MyJsonConverter((string, string[])[] filters)
    {
        Filters = filters;
    }

    private (string, string[])[] Filters { get; }

    public override void WriteJson(JsonWriter writer, IDictionary<string, string> value, JsonSerializer serializer)
    {
        foreach (var (k, v) in value)
        {
            var tuple = Filters.FirstOrDefault(s => s.Item1 == writer.Path);
            if (tuple != default && tuple.Item2.Contains(k))
                continue; // if Filters has path and key then NOP

            writer.WriteStartObject();
            writer.WritePropertyName(k);
            writer.WriteValue(v);
            writer.WriteEndObject();
        }
    }

    public override IDictionary<string, string> ReadJson(
        JsonReader reader,
        Type objectType,
        IDictionary<string, string> existingValue,
        bool hasExistingValue,
        JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

public class MyDataContractResolver : DefaultContractResolver
{
    public MyDataContractResolver((string, string[])[] filters)
    {
        Filters = filters;
    }

    private (string, string[])[] Filters { get; }

    protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
    {
        return new MyJsonDictionaryContract(objectType, Filters);
    }
}

The example:这个例子:

internal static class Program
{
    private static void Main(string[] args)
    {
        var data = new MyData
        {
            Dictionary = new Dictionary<string, string>
            {
                {"public", "abcd"},
                {"private", "abcd"}
            },
            Inner = new MyDataInner
            {
                Dictionary = new Dictionary<string, string>
                {
                    {"public", "abcd"},
                    {"private", "abcd"}
                }
            }
        };

        var filters = new[]
        {
            (ExpressionUtils.GetPath(data, s => s.Dictionary), new[] {"private"}),
            (ExpressionUtils.GetPath(data, s => s.Inner.Dictionary), new[] {"public"})
        };

        var resolver = new MyDataContractResolver(filters);

        var settings = new JsonSerializerSettings
        {
            Formatting = Formatting.Indented,
            ContractResolver = resolver
        };

        var json = JsonConvert.SerializeObject(data, settings);
    }
}

internal static class ExpressionUtils
{
    /// <summary>
    ///     expression 2 path, e.g. class.prop1.prop2
    /// </summary>
    public static string GetPath<TSource, TProperty>(TSource source, Expression<Func<TSource, TProperty>> property)
    {
        // https://stackoverflow.com/a/1667533/361899

        var expression = property.Body.NodeType switch
        {
            ExpressionType.Convert => (property.Body is UnaryExpression ue ? ue.Operand : null) as MemberExpression,
            ExpressionType.ConvertChecked =>
                (property.Body is UnaryExpression ue ? ue.Operand : null) as MemberExpression,
            _ => property.Body as MemberExpression
        };

        var stack = new Stack<string>();

        while (expression != null)
        {
            stack.Push(expression.Member.Name);
            expression = expression.Expression as MemberExpression;
        }

        var path = string.Join(".", stack);

        return path;
    }
}

Result without it:没有它的结果:

{
  "Dictionary": {
    "public": "abcd",
    "private": "abcd"
  },
  "Inner": {
    "Dictionary": {
      "public": "abcd",
      "private": "abcd"
    }
  }
}

Result with it:结果与它:

{
  "Dictionary": {
    "public": "abcd"
  },
  "Inner": {
    "Dictionary": {
      "private": "abcd"
    }
  }
}

So there you are, you can filter by key, but target any member whether it's nested or not.所以你是这样的,你可以按键过滤,但无论是否嵌套,都可以定位任何成员。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM