Json.Masker.SystemTextJson
1.1.11
See the version list below for details.
dotnet add package Json.Masker.SystemTextJson --version 1.1.11
NuGet\Install-Package Json.Masker.SystemTextJson -Version 1.1.11
<PackageReference Include="Json.Masker.SystemTextJson" Version="1.1.11" />
<PackageVersion Include="Json.Masker.SystemTextJson" Version="1.1.11" />
<PackageReference Include="Json.Masker.SystemTextJson" />
paket add Json.Masker.SystemTextJson --version 1.1.11
#r "nuget: Json.Masker.SystemTextJson, 1.1.11"
#:package Json.Masker.SystemTextJson@1.1.11
#addin nuget:?package=Json.Masker.SystemTextJson&version=1.1.11
#tool nuget:?package=Json.Masker.SystemTextJson&version=1.1.11
Json.Masker
Json.Masker is a simple library mask sensitive values when serializing them to JSON. Mark a property with [Sensitive], flip the ambient masking context on, and the library does the rest—no custom DTOs, no brittle string hacks.
The repository contains the core abstractions plus concrete integrations for both Newtonsoft.Json and System.Text.Json so you can keep your favorite serializer and still get automatic masking.
Packages
| Package | Description |
|---|---|
Json.Masker.Abstract |
Attribute, masking strategies, context accessors, and the default masking service. This transient package isn't published to NuGet; it just keeps the shared bits tidy for the two adapters. |
Json.Masker.Newtonsoft |
Plug-and-play ContractResolver that wraps sensitive members before Newtonsoft writes them out. |
Json.Masker.SystemTextJson |
JsonTypeInfo modifier that swaps in masking converters when the built-in source generator runs. |
Json.Masker.AspNet |
Middleware and helpers that toggle masking per request and hook the System.Text.Json stack into ASP.NET Core. |
Json.Masker.AspNet.Newtonsoft |
Middleware and helpers that wire the Newtonsoft.Json integration into ASP.NET Core's MVC pipeline. |
Samples
The samples/ folder contains minimal console applications that demonstrate how to enable masking for both serializers:
Json.Masker.Sample.SystemTextJsonshows how to plug theMaskingTypeInfoModifierintoSystem.Text.Json.Json.Masker.Sample.Newtonsoftwires up theMaskingContractResolverforNewtonsoft.Json.
Run them with dotnet run --project <sample-project> to see the masked and unmasked payloads side by side.
All packages version together and ship to NuGet whenever main is updated.
Quick start
Install the package that matches your serializer:
dotnet add package Json.Masker.Newtonsoft
# or
dotnet add package Json.Masker.SystemTextJson
# optional web helpers
dotnet add package Json.Masker.AspNet
dotnet add package Json.Masker.AspNet.Newtonsoft
The ASP.NET Core helpers are optional but recommended whenever you want to flip masking on or off per request without writing boilerplate middleware.
Wire it up
Pick the style that fits your app:
Use the plumbing extension if you're already on
Microsoft.Extensions.DependencyInjection. Callservices.AddJsonMasking()from the matching package and it will register:IMaskingServiceas a singleton, using either the built-inDefaultMaskingServiceor the instance you provide throughMaskingOptions.IJsonMaskingConfiguratoras a singleton, wired to the serializer-specific implementation (NewtonsoftJsonMaskingConfiguratororSystemTextJsonMaskingConfigurator).
Once that extension is in place you can inject
IJsonMaskingConfiguratorwherever you configure the serializer (for example in MVC setup) and let it bolt on the masking bits for you. Pair it withapp.UseNewtonsoftJsonMasking()orapp.UseTextJsonMasking()from the ASP.NET helper packages to flip masking on and off per request.Wire it manually if you're configuring the serializer yourself or aren't using DI. Just new up
DefaultMaskingService(or your own implementation) and pass it into the resolver/modifier shown in the manual samples below.The options expose a writeable
MaskingServiceproperty, so you can swap in your own masking logic:builder.Services.AddJsonMasking(options => { options.MaskingService = new MyCustomMaskingService(); });
Either way, masking kicks in once you mark your models and flip the context switch.
Mask the model
- Annotate the bits of your model that shouldn't leak.
The optional string parameter on <code>[Sensitive]</code> uses <code>#</code> to copy a character from the source value and <code>*</code> to mask it, allowing simple custom formats without a bespoke masking service.public class Customer { public string Name { get; set; } = string.Empty; [Sensitive(MaskingStrategy.Creditcard)] public string CreditCard { get; set; } = string.Empty; [Sensitive(MaskingStrategy.Ssn)] public string SSN { get; set; } = string.Empty; [Sensitive(MaskingStrategy.Email)] public string Email { get; set; } = string.Empty; [Sensitive(MaskingStrategy.Iban)] public string BankAccount { get; set; } = string.Empty; [Sensitive] public int Age { get; set; } [Sensitive(MaskingStrategy.Redacted)] public List<string> Hobbies { get; set; } = []; [Sensitive("##-****-####")] public string LoyaltyNumber { get; set; } = string.Empty; } - Turn masking on for the current request or operation by setting the ambient context (middleware is a great place for this):
MaskingContextAccessor.Set(new MaskingContext { Enabled = true }); - Serialize like normal.
Plumbing extension in action
var builder = WebApplication.CreateBuilder(args);
// using Microsoft.AspNetCore.Mvc;
// using Microsoft.Extensions.DependencyInjection;
// using Microsoft.Extensions.Options;
builder.Services.AddControllers()
.AddNewtonsoftJson();
builder.Services.AddJsonMasking(options =>
{
// Replace with your own IMaskingService if you need custom behavior.
options.MaskingService = new DefaultMaskingService();
});
builder.Services
.AddOptions<MvcNewtonsoftJsonOptions>()
.Configure<IJsonMaskingConfigurator>((opts, configurator) =>
configurator.Configure(opts.SerializerSettings));
var app = builder.Build();
app.UseNewtonsoftJsonMasking();
app.MapControllers();
app.Run();
Swap out AddNewtonsoftJson() for the default System.Text.Json stack and tweak the options wiring:
builder.Services.AddControllers();
// using Microsoft.AspNetCore.Http.Json;
// using Microsoft.Extensions.DependencyInjection;
// using Microsoft.Extensions.Options;
builder.Services.AddJsonMasking();
builder.Services
.AddOptions<JsonOptions>()
.Configure<IJsonMaskingConfigurator>((opts, configurator) =>
configurator.Configure(opts.SerializerOptions));
var app = builder.Build();
app.UseTextJsonMasking();
app.MapControllers();
app.Run();
In both cases the extension ensures DI knows about:
- An
IMaskingServicesingleton (default or custom). - An
IJsonMaskingConfiguratorsingleton that knows how to modify the serializer's settings/options.
After that the configurator will plug itself into MVC's JSON settings during startup. You can apply the same pattern to worker services, minimal APIs, or any custom bootstrapping code that uses the Options system.
Manual samples
Newtonsoft.Json
var maskingService = new DefaultMaskingService();
var settings = new JsonSerializerSettings
{
ContractResolver = new MaskingContractResolver(maskingService)
};
var json = JsonConvert.SerializeObject(customer, Formatting.Indented, settings);
Masked output ends up looking like:
{
"Name": "Alice",
"CreditCard": "****-****-****-1234",
"SSN": "***-**-6789",
"Email": "a*****@g****.com",
"BankAccount": "GB** **** **** **** 1234",
"Age": "****",
"Hobbies": ["<redacted>", "<redacted>"],
"LoyaltyNumber": "12-****-3456"
}
System.Text.Json
var maskingService = new DefaultMaskingService();
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver
{
Modifiers = { new MaskingTypeInfoModifier(maskingService).Modify }
}
};
var json = JsonSerializer.Serialize(customer, options);
Behind the scenes the modifier swaps in custom converters that call the masking service for both scalars and collections, so you get the same result.
Contribution guide
Local workflow tips
- Run
dotnet restore,dotnet build, anddotnet testfrom the repository root to validate changes. The solution and test project share the same SDK version viaglobal.json, so you will get consistent compiler behavior. - Use
./install-dependencies.sh(or the PowerShell equivalent) to install the expected .NET SDK and the repository'spre-commithooks. - Execute
./run-pre-commit.shlocally before opening a PR. It runs analyzers and formatters so CI does not fail on style issues.
Extending masking behavior
The built-in DefaultMaskingService is intentionally straightforward so you can replace it when needed:
- Implement
IMaskingServicein a new class with whatever masking rules your domain requires. - Register your implementation via
MaskingOptions(as shown earlier) or by adding it to the DI container. - Add tests under
tests/Json.Masker.Teststo describe the new behavior. The existing tests include helpers for exercising both serializers.
If the new behavior is reusable, consider contributing it back by adding a new value to MaskingStrategy and wiring it into DefaultMaskingService.
Overriding the built-in service
When you only need to tweak a specific masking rule, inherit from DefaultMaskingService and override the protected masking helpers (for example MaskCreditCard or MaskEmail). The top-level Mask method delegates to those helpers, so overriding one method updates every serializer integration automatically. Remember to register your derived service via DI so the adapters pick it up.
Implementation guide
Adding another serializer integration
Both existing adapters wrap IMaskingService behind an IJsonMaskingConfigurator. To support a new serializer:
- Create a project under
src/that referencesJson.Masker.Abstract. - Implement
IJsonMaskingConfiguratorto hook the masking service into the serializer's configuration model. - Expose a
AddJsonMaskingextension (mirroring the current adapters) so consumers get a consistent experience. - Cover the integration with tests that exercise the new serializer and ensure sensitive members are masked.
Following this pattern keeps the developer ergonomics identical regardless of serializer choice.
Masking strategies
The built-in DefaultMaskingService supports a few common strategies:
| Strategy | Result |
|---|---|
MaskingStrategy.Default |
**** |
MaskingStrategy.Creditcard |
****-****-****-1234 (keeps the last four digits) |
MaskingStrategy.Ssn |
***-**-6789 |
MaskingStrategy.Redacted |
<redacted> |
MaskingStrategy.Email |
a*****@d****.com (keeps the first character and domain suffix) |
MaskingStrategy.Iban |
GB** **** **** **** 1234 (keeps the country code and last four characters) |
You can roll your own IMaskingService and plug it in through MaskingOptions if you want custom behavior (different patterns, role-based rules, etc.).
Masking is also opt-in per request—leave MaskingContext.Enabled set to false and the library will just write the original values. That’s handy for internal tooling, auditing flows, or when a user has the right to view their own data.
Building locally
dotnet restore
dotnet build
dotnet test
Tip: run ./install-dependencies.sh (or the PowerShell equivalent on Windows) to install consistent tooling like the matching .NET SDK and the pre-commit hooks.
Releasing
Releases are automated through GitHub Actions:
- Open a pull request with conventional commits.
- Create a PR into
main - Once it is merged, a Github Action workflow would publish to NuGet and create downloadable artifacts.
Performance
The current performance benchmarks look like this:
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|
| Plain_SystemTextJson (no masking!) | 501.6 ns | 10.10 ns | 21.30 ns | 1.00 | 0.00 | 0.0591 | 752 B | 1.00 |
| JsonMasker_Newtonsoft (Json.Masker) | 990.0 ns | 14.70 ns | 29.01 ns | 1.97 | 0.09 | 0.1698 | 2152 B | 2.86 |
| JsonMasker_SystemTextJson (Json.Masker) | 801.0 ns | 15.44 ns | 21.14 ns | 1.62 | 0.07 | 0.0515 | 648 B | 0.86 |
| JsonDataMasking_Newtonsoft | 8,085.0 ns | 161.61 ns | 311.37 ns | 16.08 | 0.98 | 0.5188 | 6761 B | 8.99 |
| JsonMasking_PayloadMasking | 7,013.2 ns | 50.64 ns | 47.37 ns | 13.90 | 0.37 | 0.4272 | 5720 B | 7.61 |
| Byndyusoft_SystemTextJson | 324.4 ns | 5.13 ns | 4.80 ns | 0.64 | 0.02 | 0.0181 | 232 B | 0.31 |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Json.Masker.Abstract (>= 1.1.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.14 | 148 | 10/17/2025 |
| 1.1.11 | 184 | 10/16/2025 |
| 1.1.9 | 195 | 10/16/2025 |
| 1.1.7 | 177 | 10/16/2025 |
| 1.1.5 | 182 | 10/16/2025 |
| 1.1.3 | 188 | 10/16/2025 |
| 1.1.1 | 187 | 10/13/2025 |
| 1.0.7 | 184 | 10/13/2025 |
| 1.0.5 | 193 | 10/12/2025 |
| 1.0.3 | 186 | 10/12/2025 |
| 1.0.0 | 188 | 10/12/2025 |
| 0.5.5 | 124 | 10/10/2025 |
| 0.5.3 | 120 | 10/10/2025 |
| 0.5.1 | 115 | 10/10/2025 |
| 0.5.0 | 121 | 10/10/2025 |
| 0.4.10 | 158 | 10/10/2025 |
| 0.4.6 | 172 | 10/10/2025 |
| 0.4.4 | 166 | 10/10/2025 |
| 0.4.2 | 163 | 10/10/2025 |
| 0.4.0 | 193 | 10/10/2025 |
See the changelog at https://github.com/myarichuk/Json.Masker/blob/main/CHANGELOG.md