System.Text.Json - 调用 JsonSerializer.Serialize() 时有条件地使用 JsonConverter 和 JsonConverterAttribute

Posted

技术标签:

【中文标题】System.Text.Json - 调用 JsonSerializer.Serialize() 时有条件地使用 JsonConverter 和 JsonConverterAttribute【英文标题】:System.Text.Json - Use JsonConverter and JsonConverterAttribute conditionally when calling JsonSerializer.Serialize() 【发布时间】: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,但它对字符串以外的任何东西都不起作用,因为这是我的转换器处理的。如果我直接在选项中应用所需的转换器,它可以工作,但会序列化为无或全部。

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

【问题讨论】:

【参考方案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 我刚刚更新了我的答案。感谢您的评论。

以上是关于System.Text.Json - 调用 JsonSerializer.Serialize() 时有条件地使用 JsonConverter 和 JsonConverterAttribute的主要内容,如果未能解决你的问题,请参考以下文章

System.Text.Json 反序列化来自 API 调用的嵌套对象 - 数据包装在父 JSON 属性中

使用 system.text.json 序列化和反序列化流

如何使用 System.Text.Json API 将流反序列化为对象

使用 System.Text.Json 从 json 文件读取到 IEnumerable

如何使用 System.Text.Json 忽略错误值

System.Text.Json.JsonSerializer.Serialize 返回空 Json 对象“”[重复]