'How to write an attribute for replacing sensitive data on serialization using System.Text.Json

I am logging my parameter models on console. In there I don't want to display the user password on login/register raw in logs, I want *. My limitation is that I need to use System.Text.Json! This is what I tried.

[AttributeUsage(AttributeTargets.Property)]
public class SensitiveDataAttribute : JsonAttribute
{
}

public class LoginModel
{
    public string? Email { get; set; }

    [SensitiveData]
    public string? Password { get; set; }

    public override string ToString() => this.ToLogJsonString();
}

public class SensitiveDataConverter : JsonConverter<string>
{
    public SensitiveDataConverter()
    {
    }

    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 attribute = value.GetType().GetTypeInfo().GetCustomAttribute<SensitiveDataAttribute>();

        if (attribute is null)
        {
            writer.WriteStringValue(value);
            return;
        }

        var secret = new String(value.Select(x => '*').ToArray());
        writer.WriteStringValue(secret);
    }
}

public static class LoggableObjectExtensions
{
    public static string ToLogJsonString(this object value)
    {
        JsonSerializerOptions options = new JsonSerializerOptions
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            PropertyNameCaseInsensitive = true,
            WriteIndented = false,
        };

        options.Converters.Add(new SensitiveDataConverter());

        return JsonSerializer.Serialize(value, options);

    }
}

At the end wanted to see the result

var model = new LoginModel
{
    Email = "[email protected]",
    Password = "Abrakadabra"
};


Console.WriteLine(model.ToLogJsonString());

I problem is that my attribute not got recognized in the SensitiveDataConverter. Any idea? thnx



Solution 1:[1]

the problem is that in your Write method public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)

the argument value has no knowledge about LoginModel. You are doing a reflection on the primitive string type to try and read the attribute that is in LoginModel. Instead you need to have your Converter tied to the LoginModel so you can inspect it's properties

public class SensitiveDataConverter : JsonConverter<LoginModel>
{
    public override LoginModel? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, LoginModel value, JsonSerializerOptions options)
    {
        foreach (var propInfo in value.GetType().GetProperties())
        {
            if (propInfo.CanRead)
            {
                var propVal = propInfo.GetValue(value, null);

                // obviously, leaving it up to you to beautify your json :)
                if (propInfo.GetCustomAttribute<SensitiveDataAttribute>() != null)
                {
                    writer.WriteStringValue("SensitiveData");
                }
                else
                {
                    writer.WriteStringValue(propVal.ToString());
                }
            }
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1