System.Text.Json - 在调用 JsonSerializer.Serialize() 时有条件地使用 JsonConverter 和 JsonConverterAttribute
System.Text.Json - Use JsonConverter and JsonConverterAttribute conditionally when calling JsonSerializer.Serialize()
我希望能够有条件地使用自定义 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);
}
}
示例 class 使用属性:
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
但这对字符串不起作用,因为这是我的转换器处理的内容。如果我直接在选项中应用所需的转换器,它可以工作,但会序列化为 none 或全部。
那么实现这个的正确方法是什么?
JsonSerializerOptions class 是密封的,所以你不能扩展它。但是,您可以为 enable/disable 这个自定义选项创建一个扩展方法。这是一个代码片段:
编辑:改进JsonSerializerOptionsExt
现在使用ConditionalWeakTable.
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"}
我希望能够有条件地使用自定义 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);
}
}
示例 class 使用属性:
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
但这对字符串不起作用,因为这是我的转换器处理的内容。如果我直接在选项中应用所需的转换器,它可以工作,但会序列化为 none 或全部。
那么实现这个的正确方法是什么?
JsonSerializerOptions class 是密封的,所以你不能扩展它。但是,您可以为 enable/disable 这个自定义选项创建一个扩展方法。这是一个代码片段:
编辑:改进JsonSerializerOptionsExt
现在使用ConditionalWeakTable.
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"}