I want to be able to conditionally use a custom JsonConverterAttribute
which in turn creates a type of custom JsonConverter
when calling JsonSerializer.Serialize()
My current implementation means that it works every time a string that has the attribute is called however i want to limit it to when a type of JsonConverter
is passed into the JsonSeriliazationOptions
My current implementation looks like this:
Converter:
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);
}
}
Attribute:
[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);
}
}
Example class using attribute:
public class SampleClass
{
[Sensitive(4)]
public string TheMaskedProperty { get; set; } = "mask me";
public string TheUnMaskedProperty { get; set; } = "dont dare mask me";
}
Given this implementation every time i call JsonSerializer.Serialize()
it will use the converter on the attributed properties, however i would like control over this and for it to be only applied when i specify a parameter or flag in the JsonSerializerOptions
. I have an implementation of this that works, however it feels more like a workaround rather than a proper implementation. Currently i am implementing it as follows:
Updated converter:
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);
}
}
New converter that is essentially used as a flag and does nothing else:
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();
}
Example use
var sampleClassInstance = new SampleClass();
var generatedJson = JsonSerializer.Serialize(sampleClassInstance, new JsonSerializerOptions { Converters = { new SensitiveFlag() } });
This does work as i expect, however as mentioned it feels like a hack rather than a proper implementation. I have tried to use a JsonConverterFactory
however that doesn't work for anything but strings as that's what my converter deals with. If i apply the required converter in the options directly, it works but will serialize for none or all.
So what is the correct way to implement this?