using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public string Name { get; set; }
public string Phone { get; set; }
public class SecretData { }
public class ExtendedStudent : Student
public SecretData SecretData { get; set; } = new();
public class RedactingContracResolver : DefaultContractResolver
readonly List<(Type type, HashSet<string> memberNames)> membersToRedact;
public RedactingContracResolver(params (Type type, string [] memberNames) [] membersToRedact)
ArgumentNullException.ThrowIfNull(membersToRedact);
this.membersToRedact = membersToRedact
.Select(p => (p.type, p.memberNames.ToHashSet()))
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
var property = base.CreateProperty(member, memberSerialization);
foreach (var toRedact in membersToRedact.Where(p => p.type.IsAssignableFrom(member.ReflectedType) == true && p.memberNames.Contains(member.Name)))
property.ValueProvider = new RedactedStringValueProvider(property.ValueProvider);
public class RedactedStringValueProvider : IValueProvider
IValueProvider? baseProvider;
public RedactedStringValueProvider(IValueProvider? baseProvider) => this.baseProvider = baseProvider;
public object? GetValue(object target)
var baseValue = baseProvider?.GetValue(target);
if (baseValue is not string s || string.IsNullOrWhiteSpace(s))
return new string('*', s.Length);
public void SetValue(object target, object? value) => throw new NotSupportedException(nameof(SetValue));
public static void Test()
static void TestStudent()
Student student = new(){ Name = "name", Phone = "1234" };
string[] mask = { "Name" };
RedactingContracResolver resolver = new((typeof(object), mask));
JsonSerializerSettings settings = new()
ContractResolver = resolver,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
var serializedString = JsonConvert.SerializeObject(student, settings);
Console.WriteLine(serializedString);
Assert.That(JToken.DeepEquals(JToken.Parse(serializedString), JToken.Parse("""{"Name":"****","Phone":"1234"}""")));
static void TestStudentOnly()
Student student = new(){ Name = "name extended", Phone = "1234" };
string[] mask = { nameof(Student.Name) };
RedactingContracResolver resolver = new((typeof(Student), mask));
JsonSerializerSettings settings = new()
ContractResolver = resolver,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
var serializedString = JsonConvert.SerializeObject(student, settings);
Console.WriteLine(serializedString);
Assert.That(JToken.DeepEquals(JToken.Parse(serializedString), JToken.Parse("""{"Name":"*************","Phone":"1234"}""")));
static void TestExtendedStudent()
ExtendedStudent student = new(){ Name = "Foo", Phone = "1234" };
string[] mask = { nameof(Student.Name), nameof(ExtendedStudent.SecretData) };
RedactingContracResolver resolver = new((typeof(Student), mask));
JsonSerializerSettings settings = new()
ContractResolver = resolver,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
var serializedString = JsonConvert.SerializeObject(student, settings);
Console.WriteLine(serializedString);
Assert.That(JToken.DeepEquals(JToken.Parse(serializedString), JToken.Parse("""{"SecretData":null,"Name":"***","Phone":"1234"}""")));
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Namespace, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");