Receber por email
← Voltar para o catálogo

dotnet-techne-serialisation

Use when selecting serialisation formats and contracts for APIs, messaging, caching, or persistence. Keywords: serialisation, System.Text.Json, source generator, protobuf, messagepack, wire compatibility, AOT.

11estrelas
Atualizado há 4 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add Metalnib/dotnet-episteme-skills

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/dotnet-techne-serialisation/svg)](https://www.skillteca.com.br/skills/dotnet-techne-serialisation)

Skills relacionadas

Ver todas de Documentos →

Alerta por categoria

Receba novas skills de Documentos toda segunda

1 email curto, só com as skills novas de Documentos. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

Serialisation in .NET

When to Use This Skill

Use this skill when:

  • Choosing a serialisation format for APIs, messaging, or persistence
  • Migrating from Newtonsoft.Json to System.Text.Json
  • Implementing AOT-compatible serialisation
  • Designing wire formats for distributed systems
  • Optimizing serialisation performance

Schema-Based vs Reflection-Based (Serialisation)

| Aspect | Schema-Based | Reflection-Based | |--------|--------------|------------------| | Examples | Protobuf, MessagePack, System.Text.Json (source gen) | Newtonsoft.Json, BinaryFormatter | | Type info in payload | No (external schema) | Yes (type names embedded) | | Versioning | Explicit field numbers/names | Implicit (type structure) | | Performance | Fast (no reflection) | Slower (runtime reflection) | | AOT compatible | Yes | No | | Wire compatibility | Excellent | Poor |

Recommendation: Use schema-based serialisation for anything that crosses process boundaries.

Format Recommendations

| Use Case | Recommended Format | Why | |----------|-------------------|-----| | REST APIs | System.Text.Json (source gen) | Standard, AOT-compatible | | gRPC | Protocol Buffers | Native format, excellent versioning | | Actor messaging | MessagePack or Protobuf | Compact, fast, version-safe | | Event sourcing | Protobuf or MessagePack | Must handle old events forever | | Caching | MessagePack | Compact, fast | | Configuration | JSON (System.Text.Json) | Human-readable | | Logging | JSON (System.Text.Json) | Structured, parseable |

Formats to Avoid

| Format | Problem | |--------|---------| | BinaryFormatter | Security vulnerabilities, deprecated, never use | | Newtonsoft.Json default | Type names in payload break on rename | | DataContractSerializer | Complex, poor versioning | | XML | Verbose, slow, complex |

System.Text.Json with Source Generators

For JSON serialisation, use System.Text.Json with source generators for AOT compatibility and performance.

Setup

// Define a JsonSerializerContext with all your types
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(OrderItem))]
[JsonSerializable(typeof(Customer))]
[JsonSerializable(typeof(List<Order>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class AppJsonContext : JsonSerializerContext { }

Usage

// Serialize with context
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

// Deserialize with context
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);

// Configure in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

Benefits

  • No reflection at runtime - All type info generated at compile time
  • AOT compatible - Works with Native AOT publishing
  • Faster - No runtime type analysis
  • Trim-safe - Linker knows exactly what's needed

Protocol Buffers (Protobuf)

Best for: Actor systems, gRPC, event sourcing, any long-lived wire format.

Setup

dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

Define Schema

// orders.proto
syntax = "proto3";

message Order {
    string id = 1;
    string customer_id = 2;
    repeated OrderItem items = 3;
    int64 created_at_ticks = 4;

    // Adding new fields is always safe
    string notes = 5;  // Added in v2 - old readers ignore it
}

message OrderItem {
    string product_id = 1;
    int32 quantity = 2;
    int64 price_cents = 3;
}

Versioning Rules

// SAFE: Add new fields with new numbers
message Order {
    string id = 1;
    string customer_id = 2;
    string shipping_address = 5;  // NEW - safe
}

// SAFE: Remove fields (old readers ignore unknown, new readers use default)
// Just stop using the field, keep the number reserved
message Order {
    string id = 1;
    // customer_id removed, but field 2 is reserved
    reserved 2;
}

// UNSAFE: Change field types
message Order {
    int32 id = 1;  // Was: string - BREAKS!
}

// UNSAFE: Reuse field numbers
message Order {
    reserved 2;
    string new_field = 2;  // Reusing 2 - BREAKS!
}

MessagePack

Best for: High-performance scenarios, compact payloads, actor messaging.

Setup

dotnet add package MessagePack
dotnet add package MessagePack.Annotations

Usage with Contracts

[MessagePackObject]
public sealed class Order
{
    [Key(0)]
    public required string Id { get; init; }

    [Key(1)]
    public required string CustomerId { get; init; }

    [Key(2)]
    public required IReadOnlyList<OrderItem> Items { get; init; }

    [Key(3)]
    public required DateTimeOffset CreatedAt { get; init; }

    // New field - old readers skip unknown keys
    [Key(4)]
    public string? Notes { get; init; }
}

// Serialize
var bytes = MessagePackSerializer.Serialize(order);

// Deserialize
var order = MessagePackSerializer.Deserialize<Order>(bytes);

AOT-Compatible Setup

// Use source generator for AOT
[MessagePackObject]
public partial class Order { }  // partial enables source gen

// Configure resolver
var options = MessagePackSerializerOptions.Standard
    .WithResolver(CompositeResolver.Create(
        GeneratedResolver.Instance,  // Generated
        StandardResolver.Instance));

Migrating from Newtonsoft.Json

Common Issues

| Newtonsoft | System.Text.Json | Fix | |------------|------------------|-----| | $type in JSON | Not supported by default | Use discriminators or custom converters | | JsonProperty | JsonPropertyName | Different attribute | | DefaultValueHandling | DefaultIgnoreCondition | Different API | | NullValueHandling | DefaultIgnoreCondition | Different API | | Private setters | Requires [JsonInclude] | Explicit opt-in | | Polymorphism | [JsonDerivedType] (.NET 7+) | Explicit discriminators |

Migration Pattern

// Newtonsoft (reflection-based)
public class Order
{
    [JsonProperty("order_id")]
    public string Id { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string? Notes { get; set; }
}

// System.Text.Json (source-gen compatible)
public sealed record Order(
    [property: JsonPropertyName("order_id")]
    string Id,

    string? Notes  // Null handling via JsonSerializerOptions
);

[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class OrderJsonContext : JsonSerializerContext { }

Polymorphism with Discriminators

// .NET 7+ polymorphism
[JsonDerivedType(typeof(CreditCardPayment), "credit_card")]
[JsonDerivedType(typeof(BankTransferPayment), "bank_transfer")]
public abstract record Payment(decimal Amount);

public sealed record CreditCardPayment(decimal Amount, string Last4) : Payment(Amount);
public sealed record BankTransferPayment(decimal Amount, string AccountNumber) : Payment(Amount);

// Serializes as:
// { "$type": "credit_card", "amount": 100, "last4": "1234" }

Wire Compatibility Patterns

Tolerant Reader

Old code must safely ignore unknown fields:

// Protobuf/MessagePack: Automatic - unknown fields skipped
// System.Text.Json: Configure to allow
var options = new JsonSerializerOptions
{
    UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};

Introduce Read Before Write

Deploy deserializers before serializers for new formats:

// Phase 1: Add deserializer (deployed everywhere)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
    "Order.V1" => DeserializeV1(data),
    "Order.V2" => Deserialize
Compartilhar X LinkedIn