1

I have a poco class containing PersonalData, like this:

public class UserInfo
{
    [PersonalData]
    public string Name { get; set; }

    [PersonalData]
    public string Address { get; set; }

    [PersonalData]
    public DateTime Dob { get; set; }

    public bool IsActive { get; set; }
}

I have many scenario's I serialize this object and what I'd like to do is mask the Pii data like this "****" during the serialization process - I'm serializing this to write to logs for example.

I've attempted to implement my own serializer with this code:

public class PersonalDataSerializer<T> : JsonConverter<T> 
    where T: new()
{
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert.HasPiiData();
    }

    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        // read here...
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        // write here...
        throw new NotImplementedException();
    }
}

I'm not sure how to implement the write method without just carrying out normal serialization then trying to work out properties with the attribute and doing some sort of string manipulation.

What would make this easier is if the custom implementation was on each property instead of the whole object.

I use this method to identify properties on a type that have the PersonalData attribute:

public static class TypeExtensions
{
    public static bool HasPiiData(this Type type)
    {
        return type.GetProperties().Any(p => p.IsPiiData());
    }

    public static bool IsPiiData(this PropertyInfo prop)
    {
        foreach (var att in prop.CustomAttributes)
        {
            if (att.AttributeType == typeof(PersonalDataAttribute))
            {
                return true;
            }
            if (att.AttributeType.Name.Contains("PersonalData"))
            {
                return true;
            }
        }

        return false;
    }
}

Any advice on how to implement custom serialization based on an attribute would be much appreciated :-)

4

1 Answer 1

1

As of .NET 7 and later, System.Text.Json supports the use of JsonTypeInfo modifiers to customize your types' serialization contracts. This allows you to apply a dummy converter that writes "****" to all properties and fields marked with attributes that have "PersonalData" in their type name. With this approach you don't need to create your own serializer for types containing personal data, you only need to create a single dummy converter to use for the personal data properties themselves.

First create the following modifier and converter:

public static partial class JsonExtensions
{
    public static Action<JsonTypeInfo> MaskPersonalData { get; } = typeInfo => 
    {
        foreach (var property in typeInfo.Properties)
            if (property.IsPiiData())
                property.CustomConverter = PersonalDataMaskingConverter.Instance;
    };

    static bool IsPiiData(this JsonPropertyInfo info) => 
        info.AttributeProvider?.GetCustomAttributes(true).Cast<Attribute>().Any(a => a.IsPiiData()) == true;

    static bool IsPiiData(this Attribute attribute) => 
        attribute is PersonalDataAttribute || attribute.GetType().Name.Contains("PersonalData", StringComparison.Ordinal);
}

public class PersonalDataMaskingConverter : JsonConverter<object>
{
    const string PersonalDataString = "****";
    public static PersonalDataMaskingConverter Instance { get; } = new ();
    
    public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) => 
        writer.WriteStringValue(PersonalDataString);
    public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        throw new NotSupportedException($"Deserialization of [PersonalData] values is not supported.");
}

Then apply the modifier when setting up your JsonSerializerOptions for logging:

JsonSerializerOptions options = new ()
{
    // Add whatever options you want here.
};
options.TypeInfoResolver = 
    (options.TypeInfoResolver ?? new DefaultJsonTypeInfoResolver())
    .WithAddedModifier(JsonExtensions.MaskPersonalData);

And now

var json = JsonSerializer.Serialize(userInfo, options);

Results in

{"Name":"****","Address":"****","Dob":"****","IsActive":true}

Notes:

Demo fiddle here.

Sign up to request clarification or add additional context in comments.

1 Comment

This question had a decent number of views so I went ahead and added an answer that's up to date with current .NET.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.