Formatters and Outputs
Learn how EasyMessages converts messages to different output formats (JSON, XML, Console, PlainText) and how to configure formatting options.
What are Formatters?
Formatters are responsible for converting Message objects into specific output formats. Each formatter implements the IMessageFormatter interface and provides consistent, customizable output.
var message = Msg.Auth.LoginFailed();
// Different output formats from the same message
var json = message.ToJson(); // JSON string
var xml = message.ToXml(); // XML string
var text = message.ToPlainText(); // Plain text
message.ToConsole(useColors: true); // Colored console output
Key Features:
- [✓] Multiple formats - JSON, XML, Console, PlainText
- [✓] Configurable output - Control what fields are included
- [✓] Extensible - Create custom formatters
- [✓] Thread-safe - Safe for concurrent use
- [✓] Performance optimized - Minimal allocations
Built-in Formatters
1. JSON Formatter
Converts messages to JSON format using System.Text.Json.
Usage:
var message = Msg.Crud.Created("User")
.WithData(new { Id = 123, Name = "John" });
var json = message.ToJson();
Default Output:
{
"code": "CRUD_001",
"type": "success",
"title": "Created Successfully",
"description": "User has been created successfully.",
"timestamp": "2026-01-09T14:30:00Z",
"httpStatusCode": 200,
"data": {
"id": 123,
"name": "John"
}
}
Features:
- Uses camelCase for property names
- Excludes null fields by default
- Compact output (not indented)
- Follows RFC 8259 JSON standard
Custom JSON Options:
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true, // Pretty print
PropertyNamingPolicy = null, // PascalCase
DefaultIgnoreCondition = JsonIgnoreCondition.Never // Include nulls
};
var json = message.ToJson(jsonOptions);
Pretty-Printed Output:
{
"code": "CRUD_001",
"type": "success",
"title": "Created Successfully",
"description": "User has been created successfully.",
"timestamp": "2026-01-09T14:30:00Z",
"httpStatusCode": 200,
"correlationId": null,
"hint": null,
"metadata": {},
"data": {
"id": 123,
"name": "John"
}
}
Common Use Cases:
- [✓] API responses
- [✓] Logging to structured logs
- [✓] Storing messages in files
- [✓] Message queues
- [✓] Client-server communication
2. XML Formatter
Converts messages to XML format.
Usage:
var message = Msg.Auth.LoginFailed()
.WithMetadata("attempt", 3);
var xml = message.ToXml();
Output:
<Message>
<Code>AUTH_001</Code>
<Type>Error</Type>
<Title>Authentication Failed</Title>
<Description>Invalid username or password.</Description>
<Timestamp>2026-01-09T14:30:00Z</Timestamp>
<HttpStatusCode>401</HttpStatusCode>
<Metadata>
<Entry>
<Key>attempt</Key>
<Value>3</Value>
</Entry>
</Metadata>
</Message>
Features:
- Well-formed XML
- Supports nested data structures
- Includes XML declaration
- UTF-8 encoding
Common Use Cases:
- [✓] Legacy system integration
- [✓] SOAP services
- [✓] XML-based message queues
- [✓] Configuration files
- [✓] Reports and documents
3. PlainText Formatter
Converts messages to human-readable plain text.
Usage:
var message = Msg.Crud.NotFound("User")
.WithMetadata("userId", 123);
var text = message.ToPlainText();
Output:
[CRUD_004] Not Found
User was not found.
Timestamp: 2026-01-09T14:30:00Z
HTTP Status: 404
Metadata:
userId: 123
Features:
- Human-readable format
- Multiline structure
- Clear section separation
- No special characters needed
Common Use Cases:
- [✓] Email notifications
- [✓] Text files/logs
- [✓] SMS messages
- [✓] Terminal output (non-colored)
- [✓] Debug output
4. Console Formatter
Converts messages to colored console output with symbols.
Usage:
var message = Msg.Auth.LoginFailed();
// With colors
message.ToConsole(useColors: true);
// Without colors
message.ToConsole(useColors: false);
Colored Output:
✗ Authentication Failed
Invalid username or password.
[2026-01-09 14:30:00] [AUTH_001]
Color Mapping:
- Success → Green ✓
- Info → Blue ℹ
- Warning → Yellow ⚠
- Error → Red ✗
- Critical → Red ‼️
Features:
- ANSI color codes
- Type-specific symbols
- Compact format
- Optional color support
Common Use Cases:
- [✓] Console applications
- [✓] CLI tools
- [✓] Development/debugging
- [✓] Background workers
- [✓] Scripts
Output Methods
Extension Methods
Every message has these output methods:
var message = Msg.Auth.LoginFailed();
// String outputs
string json = message.ToJson();
string xml = message.ToXml();
string text = message.ToPlainText();
// Console output (void)
message.ToConsole(useColors: true);
// Generic format
string custom = message.ToFormat("myformat");
// ASP.NET Core - serializable response object
object responseObj = message.ToJsonObject();
ToJson()
// Default JSON
var json = message.ToJson();
// Custom JSON options
var json = message.ToJson(new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
ToXml()
// Standard XML output
var xml = message.ToXml();
// XML is not customizable (uses XmlSerializer defaults)
ToPlainText()
// Plain text output
var text = message.ToPlainText();
ToConsole()
// With colors (default)
message.ToConsole();
message.ToConsole(useColors: true);
// Without colors
message.ToConsole(useColors: false);
ToFormat()
// Use registered custom formatter
FormatterRegistry.Register("csv", () => new CsvFormatter());
var csv = message.ToFormat("csv");
ToJsonObject() (ASP.NET Core)
// Returns a serializable object for use with StatusCode() in controllers
[HttpGet]
public IActionResult Get()
{
var msg = Msg.Crud.Retrieved("Users").WithData(users);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
// Or for Minimal APIs
app.MapGet("/users", () =>
{
var msg = Msg.Crud.Retrieved("Users").WithData(users);
return Results.Json(msg.ToJsonObject(), statusCode: msg.HttpStatusCode ?? 200);
});
Formatter Configuration
FormatterOptions
Control what fields are included in formatted output:
public class FormatterOptions
{
public bool IncludeTimestamp { get; set; } = true;
public bool IncludeCorrelationId { get; set; } = true;
public bool IncludeHttpStatusCode { get; set; } = true;
public bool IncludeMetadata { get; set; } = true;
public bool IncludeData { get; set; } = true;
public bool IncludeParameters { get; set; } = true;
public bool IncludeHint { get; set; } = true;
public bool IncludeNullFields { get; set; } = false;
}
Global Configuration
Configure default formatting for all messages:
using RecurPixel.EasyMessages.Configuration;
// Custom configuration
FormatterConfiguration.Configure(options =>
{
options.IncludeTimestamp = false;
options.IncludeHttpStatusCode = false;
options.IncludeMetadata = true;
options.IncludeData = true;
options.IncludeNullFields = false;
});
Built-in Presets
Use predefined configurations:
// Minimal - Only essential fields
FormatterConfiguration.SetDefaultOptions(FormatterConfiguration.Minimal);
// Verbose - All fields except nulls
FormatterConfiguration.SetDefaultOptions(FormatterConfiguration.Verbose);
// Debug - Everything including nulls
FormatterConfiguration.SetDefaultOptions(FormatterConfiguration.Debug);
Minimal Preset:
// Includes only: code, type, title, description
{
"code": "AUTH_001",
"type": "error",
"title": "Authentication Failed",
"description": "Invalid username or password."
}
Verbose Preset:
// Includes: all fields except nulls
{
"code": "AUTH_001",
"type": "error",
"title": "Authentication Failed",
"description": "Invalid username or password.",
"timestamp": "2026-01-09T14:30:00Z",
"httpStatusCode": 401,
"metadata": {},
"data": null // Excluded because null
}
Debug Preset:
// Includes: everything, including nulls
{
"code": "AUTH_001",
"type": "error",
"title": "Authentication Failed",
"description": "Invalid username or password.",
"timestamp": "2026-01-09T14:30:00Z",
"correlationId": null,
"httpStatusCode": 401,
"hint": null,
"metadata": {},
"data": null,
"parameters": {}
}
Output Comparison
Same Message, Different Formats
Message:
var message = Msg.Crud.Created("User")
.WithData(new { Id = 123, Name = "John" })
.WithMetadata("source", "API");
JSON Output:
{
"code": "CRUD_001",
"type": "success",
"title": "Created Successfully",
"description": "User has been created successfully.",
"timestamp": "2026-01-09T14:30:00Z",
"httpStatusCode": 200,
"metadata": {
"source": "API"
},
"data": {
"id": 123,
"name": "John"
}
}
XML Output:
<Message>
<Code>CRUD_001</Code>
<Type>Success</Type>
<Title>Created Successfully</Title>
<Description>User has been created successfully.</Description>
<Timestamp>2026-01-09T14:30:00Z</Timestamp>
<HttpStatusCode>200</HttpStatusCode>
<Metadata>
<Entry>
<Key>source</Key>
<Value>API</Value>
</Entry>
</Metadata>
<Data>
<Id>123</Id>
<Name>John</Name>
</Data>
</Message>
PlainText Output:
[CRUD_001] Created Successfully
User has been created successfully.
Timestamp: 2026-01-09T14:30:00Z
HTTP Status: 200
Metadata:
source: API
Data:
{
"id": 123,
"name": "John"
}
Console Output:
✓ Created Successfully
User has been created successfully.
[2026-01-09 14:30:00] [CRUD_001]
Custom Formatters
Creating a Custom Formatter
Implement IMessageFormatter or extend MessageFormatterBase:
using RecurPixel.EasyMessages.Formatters;
public class CsvFormatter : MessageFormatterBase
{
protected override string FormatCore(Message message)
{
return $"{message.Code},{message.Type},{message.Title},{message.Description}";
}
protected override object FormatAsObject(Message message)
{
return new[]
{
message.Code,
message.Type.ToString(),
message.Title,
message.Description
};
}
}
Registering Custom Formatters
// Register the formatter
FormatterRegistry.Register("csv", () => new CsvFormatter());
// Use it
var csv = message.ToFormat("csv");
Example: Markdown Formatter
public class MarkdownFormatter : MessageFormatterBase
{
protected override string FormatCore(Message message)
{
var sb = new StringBuilder();
// Header
var symbol = message.Type switch
{
MessageType.Success => "✅",
MessageType.Info => "ℹ️",
MessageType.Warning => "⚠️",
MessageType.Error => "❌",
MessageType.Critical => "🔴",
_ => "•"
};
sb.AppendLine($"# {symbol} {message.Title}");
sb.AppendLine();
sb.AppendLine($"> {message.Description}");
sb.AppendLine();
// Details
sb.AppendLine("## Details");
sb.AppendLine($"- **Code:** `{message.Code}`");
sb.AppendLine($"- **Type:** {message.Type}");
sb.AppendLine($"- **Timestamp:** {message.Timestamp:yyyy-MM-dd HH:mm:ss}");
if (message.HttpStatusCode.HasValue)
sb.AppendLine($"- **HTTP Status:** {message.HttpStatusCode}");
// Metadata
if (message.Metadata.Any())
{
sb.AppendLine();
sb.AppendLine("## Metadata");
foreach (var (key, value) in message.Metadata)
{
sb.AppendLine($"- **{key}:** {value}");
}
}
// Data
if (message.Data != null)
{
sb.AppendLine();
sb.AppendLine("## Data");
sb.AppendLine("```json");
sb.AppendLine(JsonSerializer.Serialize(message.Data, new JsonSerializerOptions { WriteIndented = true }));
sb.AppendLine("```");
}
return sb.ToString();
}
protected override object FormatAsObject(Message message)
{
return FormatCore(message);
}
}
// Register and use
FormatterRegistry.Register("markdown", () => new MarkdownFormatter());
var md = message.ToFormat("markdown");
Markdown Output:
# [✓] Created Successfully
> User has been created successfully.
## Details
- **Code:** `CRUD_001`
- **Type:** Success
- **Timestamp:** 2026-01-09 14:30:00
- **HTTP Status:** 200
## Metadata
- **source:** API
## Data
```json
{
"id": 123,
"name": "John"
}
---
## Format Selection Guide
| Scenario | Recommended Format | Why |
|----------|-------------------|-----|
| **API Responses** | JSON | Standard, widely supported, compact |
| **Logging** | JSON | Structured logging, searchable |
| **Console Apps** | Console | Colored, human-readable |
| **Email Notifications** | PlainText | Simple, no formatting required |
| **Legacy Systems** | XML | SOAP, enterprise integration |
| **File Storage** | JSON | Compact, easy to parse |
| **Message Queues** | JSON | Standard, platform-agnostic |
| **Reports** | Custom (PDF, HTML) | Create custom formatter |
| **SMS** | PlainText | Character-limited, simple |
| **Debugging** | Console or JSON (pretty) | Human-readable |
---
## Best Practices
### [✓] DO:
**1. Use appropriate format for context**
```csharp
// API - use ToJsonObject() with StatusCode()
var msg = Msg.Crud.Created("User").WithData(user);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
// Console - use Console formatter
Msg.System.Processing()
.ToConsole(useColors: true);
// Logging - use JSON for structure
_logger.LogInformation(message.ToJson());
2. Configure formatting globally at startup
// Program.cs or Startup
FormatterConfiguration.SetDefaultOptions(
Environment.IsDevelopment()
? FormatterConfiguration.Debug
: FormatterConfiguration.Minimal
);
3. Use pretty JSON for debugging
#if DEBUG
var json = message.ToJson(new JsonSerializerOptions
{
WriteIndented = true
});
Console.WriteLine(json);
#endif
4. Include relevant fields based on environment
// Production - minimal
FormatterConfiguration.Configure(options =>
{
options.IncludeTimestamp = true;
options.IncludeMetadata = false; // May contain sensitive data
options.IncludeNullFields = false;
});
// Development - verbose
FormatterConfiguration.Configure(options =>
{
options.IncludeTimestamp = true;
options.IncludeMetadata = true;
options.IncludeNullFields = true; // Debug everything
});
5. Register custom formatters at startup
// Application startup
FormatterRegistry.Register("csv", () => new CsvFormatter());
FormatterRegistry.Register("markdown", () => new MarkdownFormatter());
[ ] DON’T:
1. Don’t mix formats inconsistently
// Bad - inconsistent API responses
public IActionResult Get1()
{
return Ok(message.ToJson()); // Returns JSON string, inconsistent
}
public IActionResult Get2()
{
var msg = Msg.Crud.Retrieved("Users").WithData(users);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject()); // Correct
}
// Good - consistent
public IActionResult Get1()
{
var msg = Msg.Crud.Retrieved("Items").WithData(items);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
public IActionResult Get2()
{
var msg = Msg.Crud.Retrieved("Products").WithData(products);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
2. Don’t include sensitive data in production
// Bad
FormatterConfiguration.Configure(options =>
{
options.IncludeMetadata = true; // May leak sensitive data
});
var failed = Msg.Auth.LoginFailed()
.WithMetadata("password", password); // [ ] Security risk!
return StatusCode(failed.HttpStatusCode ?? 401, failed.ToJsonObject());
// Good
FormatterConfiguration.Configure(options =>
{
options.IncludeMetadata = Environment.IsDevelopment();
});
var failed = Msg.Auth.LoginFailed()
.WithMetadata("username", username)
.WithMetadata("ipAddress", ipAddress);
return StatusCode(failed.HttpStatusCode ?? 401, failed.ToJsonObject());
3. Don’t forget to configure JSON options
// Bad - default options may not match your API
var json = message.ToJson(); // Uses internal defaults
// Good - explicit options
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
var json = message.ToJson(jsonOptions);
4. Don’t use Console formatter for APIs
// Bad - console output in API
[HttpGet]
public IActionResult Get()
{
var message = Msg.Crud.Retrieved("Users");
message.ToConsole(); // [ ] Writes to console, not response
return Ok();
}
// Good
[HttpGet]
public IActionResult Get()
{
var msg = Msg.Crud.Retrieved("Users").WithData(users);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
5. Don’t create formatters in hot paths
// Bad - creates formatter on every request
public string GetJson()
{
var formatter = new JsonFormatter(); // [ ] Allocation
return formatter.Format(message);
}
// Good - use extension method (uses cached formatter)
public string GetJson()
{
return message.ToJson(); // [✓] Efficient
}
Performance Considerations
Formatter Performance
Operation | Time | Allocations
-----------------------|-------------|-------------
ToJson() | ~1-3 μs | ~560 B
ToXml() | ~5-10 μs | ~2 KB
ToPlainText() | ~2-5 μs | ~800 B
ToConsole() | ~3-7 μs | ~1 KB
Optimization Tips
- Cache formatters - Don’t create new instances unnecessarily
- Use extension methods - Built-in caching
- Configure once - Set FormatterOptions at startup
- Minimize included fields - Fewer fields = faster
- Use Minimal preset in production - Smallest output
Troubleshooting
JSON Output Missing Fields
Problem: Some fields are null in JSON output
Solution: Check formatter configuration
// Include null fields
FormatterConfiguration.Configure(options =>
{
options.IncludeNullFields = true;
});
// Or use Debug preset
FormatterConfiguration.SetDefaultOptions(FormatterConfiguration.Debug);
Console Colors Not Working
Problem: Console output not showing colors
Solution: Disable colors or check terminal support
// Disable colors
message.ToConsole(useColors: false);
// Or check if terminal supports ANSI
if (Console.IsOutputRedirected)
{
message.ToConsole(useColors: false);
}
else
{
message.ToConsole(useColors: true);
}
Custom Formatter Not Found
Problem: ToFormat("myformat") throws exception
Solution: Register formatter first
// Register before use
FormatterRegistry.Register("myformat", () => new MyFormatter());
// Then use
var output = message.ToFormat("myformat");
Next Steps
- Interceptors - Modify messages before formatting
- Build Custom Formatters Guide - Step-by-step tutorial
- Architecture Overview - Understand the complete system
Questions? Ask in Discussions