【问题标题】:System.Text.Json - Use JsonConverter and JsonConverterAttribute conditionally when calling JsonSerializer.Serialize()System.Text.Json - 调用 JsonSerializer.Serialize() 时有条件地使用 JsonConverter 和 JsonConverterAttribute
【发布时间】:2021-08-25 22:48:38
【问题描述】:

我希望能够有条件地使用自定义JsonConverterAttribute,从而在调用JsonSerializer.Serialize() 时创建一种自定义JsonConverter

我当前的实现意味着每次调用具有该属性的字符串时它都会起作用,但是我想将其限制为将JsonConverter 类型传递到JsonSeriliazationOptions

我当前的实现如下所示:

转换器:

    public class SensitiveConverter : JsonConverter<string?>
    {
        public int MaskLength { get; }

        public SensitiveConverter(int maskLength = 4)
        {
            MaskLength = maskLength;
        }

        public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.GetString();
        }

        public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
        {
            //do something with maskLength here
            writer.WriteStringValue(value);
        }
    }

属性:

    [AttributeUsage(AttributeTargets.Property)]
    public class SensitiveAttribute : JsonConverterAttribute
    {
        public int MaskLength { get; }

        public SensitiveAttribute(int maskLength = 0)
        {
            MaskLength = maskLength;
        }

        public override JsonConverter? CreateConverter(Type typeToConvert)
        {
            return new SensitiveConverter(MaskLength);
        }
    }

使用属性的示例类:

    public class SampleClass
    {
        [Sensitive(4)]
        public string TheMaskedProperty { get; set; } = "mask me";

        public string TheUnMaskedProperty { get; set; } = "dont dare mask me";
    }

每次我调用JsonSerializer.Serialize() 时,鉴于此实现,它将在属性属性上使用转换器,但是我希望对此进行控制,并且仅当我在JsonSerializerOptions 中指定参数或标志时才应用它。我有一个可行的实现,但是感觉更像是一种解决方法,而不是正确的实现。目前我正在实施如下:

更新的转换器:

    public class SensitiveConverter : JsonConverter<string?>
    {
        public int MaskLength { get; }

        public SensitiveConverter(int maskLength = 4)
        {
            MaskLength = maskLength;
        }

        public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.GetString();
        }

        public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
        {
            var exists = options.Converters.Any(e => e.GetType() == typeof(SensitiveFlag));

            if (!exists)
                return;

            //do something with maskLength here
            writer.WriteStringValue(value);
        }
    }

新的转换器,基本上用作标志,不做任何其他事情:

    public class SensitiveFlag : JsonConverter<object>
    {
        public override bool CanConvert(Type typeToConvert) => false;


        public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotImplementedException();


        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => throw new NotImplementedException();
    }

示例使用

var sampleClassInstance = new SampleClass();
var generatedJson = JsonSerializer.Serialize(sampleClassInstance, new JsonSerializerOptions { Converters = { new SensitiveFlag() } });

这确实像我预期的那样工作,但是正如我所提到的,它感觉像是一种 hack 而不是正确的实现。我曾尝试使用JsonConverterFactory,但它对字符串以外的任何东西都不起作用,因为这是我的转换器处理的。如果我直接在选项中应用所需的转换器,它可以工作,但会序列化为无或全部。

那么实现这个的正确方法是什么?

【问题讨论】:

    标签: c# json .net-core serialization system.text.json


    【解决方案1】:

    JsonSerializerOptions 类是密封的,所以你不能扩展它。但是,您可以创建一个扩展方法来启用/禁用此自定义选项。这是一个代码片段:

    编辑:现在使用ConditionalWeakTable 改进JsonSerializerOptionsExt

    public static class JsonSerializerOptionsExt
    {
        private static readonly ConditionalWeakTable<object, object?> CwtUseSensitive = new();
        
        public static JsonSerializerOptions UseSensitive(this JsonSerializerOptions options)
        {
            CwtUseSensitive.AddOrUpdate(options, null);
            return options;
        }
    
        public static bool HasSensitive(this JsonSerializerOptions options) =>
            CwtUseSensitive.TryGetValue(options, out _);
    }
    

    您可以保留属性的实现。

    [AttributeUsage(AttributeTargets.Property)]
    public class SensitiveAttribute : JsonConverterAttribute
    {
        public int MaskLength { get; }
    
        public SensitiveAttribute(int maskLength = 0)
        {
            MaskLength = maskLength;
        }
    
        public override JsonConverter? CreateConverter(Type typeToConvert)
        {
            return new SensitiveConverter(MaskLength);
        }
    }
    

    这里是SensitiveConverter

    public class SensitiveConverter : JsonConverter<string?>
    {
        public int MaskLength { get; }
    
        public SensitiveConverter(int maskLength = 4)
        {
            MaskLength = maskLength;
        }
    
        public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            return reader.GetString();
        }
    
        public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
        {
            if (options.IsSensitive())
            {
                //do something with MaskLength here
                //example: replace the char by _
                if (!string.IsNullOrWhiteSpace(value) && MaskLength > 0)
                {
                    var sb = new StringBuilder(value);
                    for (var i = 0; i < MaskLength && i < sb.Length; sb[i++] = '_') ;
                    value = sb.ToString();
                }
            }
    
            writer.WriteStringValue(value);
        }
    }
    

    这是一个演示(部分片段已被删除以使其更短):

    ...
    public class SampleClass
    {
        [Sensitive(4)]
        public string TheMaskedProperty { get; set; } = "mask me";
    
        public string TheUnMaskedProperty { get; set; } = "dont dare mask me";
    }
    
    ...
    var p = new SampleClass();
    var output = JsonSerializer.Serialize(p, new JsonSerializerOptions().UseSensitive());
    ...
    

    输出是: {"TheMaskedProperty":"____ me","TheUnMaskedProperty":"dont dare mask me"}

    【讨论】:

    • @pinkfloydx33 我刚刚更新了我的答案。感谢您的评论。
    猜你喜欢
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 2022-12-14
    • 2020-05-01
    • 1970-01-01
    • 2022-01-12
    相关资源
    最近更新 更多