Search code examples
c#mask

How to mask a string using different parameters in an effective and fast way


I'd like to mask a string using different parameters like percentage of char to be masked, the mask character and the position to apply the mask (In the beginning, in the middle or at the end of the string). I've come up with a solution but I presume that this is not the best solution. This is my code:

public static string MaskChars(this string value, char maskToApply = 'X', int percentToApply = 25, MaskOption maskOptions = MaskOption.InTheMiddleOfString)
    {
        string valueTrimmed = value.Trim();
        int len = valueTrimmed.Length;

        if (len == 0)
            return Empty;
        if (percentToApply >= 100)
            return maskToApply.ToString(CultureInfo.InvariantCulture).Replicate(len);

        var charsToMask = (int)Math.Round((decimal)(percentToApply * len) / 100);
        if (charsToMask == 0)
            charsToMask = 1;

        int top = len - charsToMask;
        int maskCounter = 0;
        var builder = new StringBuilder(len);

        for (int i = 0; i < len; i++)
        {
            if (maskCounter < charsToMask)
            {
                switch (maskOptions)
                {
                    // Apply mask in the middle of the string
                    case MaskOption.InTheMiddleOfString:
                        if (i >= charsToMask && i < top)
                        {
                            builder.Append(maskToApply);
                            maskCounter++;
                        }
                        break;
                    // Apply mask at the begining of the string
                    case MaskOption.AtTheBeginingOfString:
                        if (i < charsToMask)
                        {
                            builder.Append(maskToApply);
                            maskCounter++;
                        }
                        break;
                    // Apply mask at the end of the string
                    case MaskOption.AtTheEndOfString:
                        if (i >= top)
                        {
                            builder.Append(maskToApply);
                            maskCounter++;
                        }
                        break;
                }
            }
            else
            {
                builder.Append(valueTrimmed[i]);
            }
        }

        return builder.ToString();
    }

where:

public enum MaskOption : byte
{
    AtTheBeginingOfString = 1,
    InTheMiddleOfString = 2,
    AtTheEndOfString = 3
}

and Replicate is a simple method to replicate a string

public static string Replicate(this string value, int count)
    {
        if (IsNullOrEmpty(value))
            return Empty;
        if (count <= 0)
            return value;

        var builder = new StringBuilder();
        builder.Append(value);
        for (int i = count; i >= 1; i--)
            builder.Append(value);

        return builder.ToString();
    }

Solution

  • First I ran your code to see what the expected behavior was, and it doesn't look right to me. Here's the test code and the output:

    var testStr = "This is my string to mask characters in!";
    Console.WriteLine(testStr.MaskChars('X', 25, Extensions.MaskOption.AtTheBeginingOfString));
    Console.WriteLine(testStr.MaskChars('X', 25, Extensions.MaskOption.InTheMiddleOfString));
    Console.WriteLine(testStr.MaskChars(maskOptions: Extensions.MaskOption.AtTheEndOfString));
    

    Output

    enter image description here

    I was under the impression that the string should remain the same length, and that the masked characters would just change position within the string. Also, I'm not sure why you're trimming the string (I wouldn't do that in this method, I would let the caller decide if they wanted to trim it first), but I left that part in.

    Here's how I would simplify the code to do that:

    public static string MaskChars(this string input, char maskChar, 
        int percentToApply, MaskOption maskOptions)
    {
        // I would remove this. The caller can trim the string first if they want.
        var result = input.Trim(); 
    
        if (result.Length == 0 || percentToApply < 1) return result;
        if (percentToApply >= 100) return new string(maskChar, result.Length);
    
        var maskLength = Math.Max((int) Math.Round(percentToApply * result.Length / 100m), 1);
        var mask = new string(maskChar, maskLength);
    
        switch (maskOptions)
        {
            case MaskOption.AtTheBeginingOfString:
                result = mask + result.Substring(maskLength);
                break;
            case MaskOption.AtTheEndOfString:
                result = result.Substring(0, result.Length - maskLength) + mask;
                break;
            case MaskOption.InTheMiddleOfString:
                var maskStart = (result.Length - maskLength) / 2;
                result = result.Substring(0, maskStart) + mask + 
                    result.Substring(maskStart + maskLength);
                break;
        }
    
        return result;
    }
    

    Output

    enter image description here

    The last thing I would do is get rid of the default argument values in the method, and create some overload methods instead that use default values for the missing arguments. This way the users can adjust the values the want (in your implementation, if they want to only change the MaskOption, then they have to re-state the other default values or use named parameters as I did above):

    private static char defaultMaskChar = 'X';
    private static MaskOption defaultMaskOption = MaskOption.InTheMiddleOfString;
    private static int defaultPercentToApply = 25;
    
    public static string MaskChars(this string input)
    {
        return MaskChars(input, defaultMaskChar);
    }
    
    public static string MaskChars(this string input, char maskChar)
    {
        return MaskChars(input, maskChar, defaultPercentToApply);
    }
    
    public static string MaskChars(this string input, int percentToApply)
    {
        return MaskChars(input, defaultMaskChar, percentToApply);
    }
    
    public static string MaskChars(this string input, MaskOption maskOption)
    {
        return MaskChars(input, defaultMaskChar, defaultPercentToApply, maskOption);
    }
    
    public static string MaskChars(this string input, char maskChar, int percentToApply)
    {
        return MaskChars(input, maskChar, percentToApply, defaultMaskOption);
    }
    
    public static string MaskChars(this string input, char maskChar, MaskOption maskOption)
    {
        return MaskChars(input, maskChar, defaultPercentToApply, maskOption);
    }
    
    public static string MaskChars(this string input, int percentToApply, MaskOption maskOption)
    {
        return MaskChars(input, defaultMaskChar, percentToApply, maskOption);
    }