System.Text.Json
Tayra integrates with System.Text.Json through a custom JsonConverterFactory that automatically encrypts [PersonalData] fields during JSON serialization and decrypts them during deserialization. Annotate once, protect everywhere -- the same attributes that protect your data in EF Core, Marten, and messaging also protect it in JSON serialization.
Install
dotnet add package Tayra.JsonInstall-Package Tayra.JsonSetup
There are two ways to register Tayra with System.Text.Json: through dependency injection or directly on a JsonSerializerOptions instance.
DI-Based Setup (Recommended)
Register Tayra core services, a key store, and the JSON converter factory in your DI container:
services.AddTayra(opts => opts.LicenseKey = licenseKey); // defaults to InMemoryKeyStore; use PostgreSQL, Vault, etc. in production
services.AddTayraJson(opts =>
{
opts.EncryptOnSerialize = true; // default
opts.DecryptOnDeserialize = true; // default
});When using ASP.NET Core, the TayraJsonConverterFactory is automatically added to the default JsonSerializerOptions used by controllers and minimal APIs. This means your API responses are protected without any additional configuration.
Standalone Setup
If you prefer to configure JsonSerializerOptions directly (e.g., in a console app or library), call UseTayra() on the options instance:
var tayra = serviceProvider.GetRequiredService<ITayra>();
var metadataCache = serviceProvider.GetRequiredService<PersonalDataMetadataCache>();
var jsonOptions = new JsonSerializerOptions();
jsonOptions.UseTayra(tayra, metadataCache);
var json = JsonSerializer.Serialize(customer, jsonOptions);
var deserialized = JsonSerializer.Deserialize<Customer>(json, jsonOptions);When to Use Standalone vs DI
Use the DI-based setup when you have a standard ASP.NET Core or hosted service application. Use the standalone setup when you need fine-grained control or are configuring serialization outside of DI (e.g., in a migration tool, file export utility, or console app).
Options
services.AddTayraJson(opts =>
{
opts.EncryptOnSerialize = true; // default
opts.DecryptOnDeserialize = true; // default
});| Property | Type | Default | Description |
|---|---|---|---|
EncryptOnSerialize | bool | true | Encrypt [PersonalData] fields before writing JSON output |
DecryptOnDeserialize | bool | true | Decrypt [PersonalData] fields after reading JSON input |
Define Your Model
Annotate your types with [DataSubjectId] and [PersonalData] to enable automatic encryption:
public class Customer
{
[DataSubjectId]
public string CustomerId { get; set; } = "";
[PersonalData]
public string Name { get; set; } = "";
[PersonalData(MaskValue = "redacted@example.com")]
public string Email { get; set; } = "";
public string AccountType { get; set; } = ""; // Not encrypted
}[DataSubjectId]onCustomerIdtells Tayra to derive the encryption key from this value.[PersonalData]onNameandEmailmarks them for encryption.- The
MaskValueparameter onEmailspecifies the value returned after crypto-shredding. AccountTypehas no attribute and is never touched by Tayra.
How It Works
The TayraJsonConverterFactory intercepts System.Text.Json serialization at the converter level:
Type inspection -- The factory consults the
PersonalDataMetadataCacheto determine if a type has any[PersonalData]properties. If a type has no PII fields, the factory returnsfalsefromCanConvert()andSystem.Text.Jsonuses its default serialization.On serialize -- When writing JSON, the converter encrypts all
[PersonalData]fields on the object, serializes the object to JSON (with ciphertext values), and then decrypts the in-memory object back to cleartext. Your application code continues working with plaintext values after serialization.On deserialize -- When reading JSON, the converter deserializes the JSON into an object (with ciphertext values in the PII fields), and then decrypts those fields. The returned object contains cleartext values.
Synchronous Bridge
System.Text.Json converters run synchronously, but Tayra's encryption engine is async. The converter uses .GetAwaiter().GetResult() to bridge this gap -- the same pattern used by Tayra's EF Core materialization interceptor. Because the crypto engine caches keys in memory, the synchronous call completes as a memory-only lookup after the initial key fetch.
Use Cases
API Responses
Combine with ASP.NET Core for automatic API response encryption. When AddTayraJson() is registered, all JSON responses containing annotated types are automatically encrypted:
app.MapGet("/api/customers/{id}", async (string id, CustomerService svc) =>
{
var customer = await svc.GetCustomerAsync(id);
return Results.Ok(customer);
// Name and Email are encrypted in the JSON response
});Event Sourcing Payloads
Protect PII in event payloads written to event stores, message queues, or log files:
var evt = new CustomerRegisteredEvent
{
CustomerId = "cust-42",
Name = "Alice Smith",
Email = "alice@example.com",
};
var json = JsonSerializer.Serialize(evt, jsonOptions);
// json contains AES-256-GCM ciphertext for Name and Email
await eventStore.AppendAsync("cust-42", json);Inter-Service Communication
When services exchange JSON payloads over HTTP, gRPC, or message queues, Tayra ensures PII is protected in transit without requiring transport-level encryption of individual fields:
var json = JsonSerializer.Serialize(payload, jsonOptions);
await httpClient.PostAsync("/api/process", new StringContent(json, Encoding.UTF8, "application/json"));File Storage
Protect PII when exporting data to JSON files for backup, reporting, or data exchange:
var customers = await db.Customers.ToListAsync();
var json = JsonSerializer.Serialize(customers, jsonOptions);
await File.WriteAllTextAsync("export.json", json);
// PII fields are encrypted in the fileCrypto-Shredding
GDPR Article 17 "right to be forgotten" works seamlessly with JSON serialization. After deleting a data subject's encryption key, any JSON that was serialized with encrypted PII becomes permanently unreadable:
// Serialize with encryption
var json = JsonSerializer.Serialize(customer, jsonOptions);
await File.WriteAllTextAsync("customer.json", json);
// Later: erase the customer's data
await cryptoEngine.DeleteAllKeysAsync("cust-42");
// Deserialize after shredding -- returns replacement values
var shredded = JsonSerializer.Deserialize<Customer>(
await File.ReadAllTextAsync("customer.json"), jsonOptions);
// shredded.Name == ""
// shredded.Email == "redacted@example.com"Irreversible
Crypto-shredding is permanent. The encryption key is deleted from the key store and cannot be recovered. Any JSON containing the shredded subject's encrypted fields will return replacement values forever.
See Also
- Getting Started -- End-to-end encryption tutorial
- ASP.NET Core Integration -- Response scrubbing middleware
- EF Core Integration -- Database-level encryption
- Key Stores -- Production key store options
