Skip to content

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

shell
dotnet add package Tayra.Json
powershell
Install-Package Tayra.Json

Setup

There are two ways to register Tayra with System.Text.Json: through dependency injection or directly on a JsonSerializerOptions instance.

Register Tayra core services, a key store, and the JSON converter factory in your DI container:

csharp
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:

csharp
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

csharp
services.AddTayraJson(opts =>
{
    opts.EncryptOnSerialize = true;   // default
    opts.DecryptOnDeserialize = true; // default
});
PropertyTypeDefaultDescription
EncryptOnSerializebooltrueEncrypt [PersonalData] fields before writing JSON output
DecryptOnDeserializebooltrueDecrypt [PersonalData] fields after reading JSON input

Define Your Model

Annotate your types with [DataSubjectId] and [PersonalData] to enable automatic encryption:

csharp
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] on CustomerId tells Tayra to derive the encryption key from this value.
  • [PersonalData] on Name and Email marks them for encryption.
  • The MaskValue parameter on Email specifies the value returned after crypto-shredding.
  • AccountType has no attribute and is never touched by Tayra.

How It Works

The TayraJsonConverterFactory intercepts System.Text.Json serialization at the converter level:

  1. Type inspection -- The factory consults the PersonalDataMetadataCache to determine if a type has any [PersonalData] properties. If a type has no PII fields, the factory returns false from CanConvert() and System.Text.Json uses its default serialization.

  2. 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.

  3. 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:

csharp
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:

csharp
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:

csharp
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:

csharp
var customers = await db.Customers.ToListAsync();
var json = JsonSerializer.Serialize(customers, jsonOptions);
await File.WriteAllTextAsync("export.json", json);
// PII fields are encrypted in the file

Crypto-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:

csharp
// 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