Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b2cf34d
feat: replace Newtonsoft.Json with System.Text.Json across the codebase
niemyjski May 28, 2026
b0c7385
Merge remote-tracking branch 'origin/main' into niemyjski/pr-2135-stj…
niemyjski May 30, 2026
257d6ab
Merge branch 'main' into feature/system-text-json-v2
niemyjski Jun 14, 2026
ecf7c7f
build: update Aspire hosting package versions
niemyjski Jun 14, 2026
49f13f5
build: suppress NuGet warning NU1903
niemyjski Jun 14, 2026
8551526
Fixed client linting
niemyjski Jun 14, 2026
df1f645
Fixed storage tests
niemyjski Jun 14, 2026
1f4e393
fix: preserve STJ compatibility APIs
niemyjski Jun 17, 2026
b80f835
fix: reject hidden billing plan changes
niemyjski Jun 17, 2026
ea7cd0f
fix: reuse problem details messages
niemyjski Jun 17, 2026
e5cd5db
fix: lazy load browser telemetry SDK
niemyjski Jun 17, 2026
81f87d6
Merge branch 'main' into feature/system-text-json-v2
niemyjski Jun 17, 2026
6aa39e9
fix: resolve Aspire package merge duplicates
niemyjski Jun 17, 2026
823ea3e
Merge branch 'main' into feature/system-text-json-v2
niemyjski Jun 17, 2026
3ec6eeb
fix: make serialization audit event refs unique
niemyjski Jun 17, 2026
58280fd
fix: address serialization PR review threads
niemyjski Jun 17, 2026
6863e4b
fix: simplify review thread cleanup
niemyjski Jun 17, 2026
d5fcb88
fix: address latest code quality suggestions
niemyjski Jun 17, 2026
affc442
test: avoid relative serializer fixture path
niemyjski Jun 17, 2026
7fb89c0
style: simplify event reference id cleanup
niemyjski Jun 17, 2026
adacec3
style: mark delta options readonly
niemyjski Jun 17, 2026
b6413fa
revert: restore readable event field lookup
niemyjski Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .agents/skills/serialization-audit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
name: serialization-audit
description: >
Use this skill when verifying serialization behavior across branches, testing for backwards
compatibility in JSON serialization changes, or comparing API request/response/storage formats
between implementations. Apply when migrating serializers (e.g., Newtonsoft to System.Text.Json),
adding new JSON converters, or changing naming policies.
---

# Serialization Audit Skill

## Overview

The serialization audit workflow generates branch-specific JSON snapshots of API behavior and compares them to detect behavioral differences. It exercises the full pipeline: API submission → queue → job processing → Elasticsearch storage → API response.

## Workflow

### 1. Run Audit Script on Both Branches

The audit runner lives at `.agents/skills/serialization-audit/scripts/audit-api-surface.ps1`. It:
- Submit events with different JSON casing conventions (snake_case, PascalCase, camelCase, mixed)
- Capture the raw request body, Elasticsearch stored document, and API response
- Save each to `audit-output/{audit-run-id}/{branch-name}/{scenario}/` with files like:
- `request.json` — what was submitted
- `elastic.json` — what was stored
- `response.json` — what the API returned

```bash
# Start Exceptionless locally first:
aspire run

# On main branch:
git checkout main
pwsh .agents/skills/serialization-audit/scripts/audit-api-surface.ps1 \
-BranchName main \
-AuditRunId live-serialization

# On feature branch:
git checkout feature/system-text-json-v2
pwsh .agents/skills/serialization-audit/scripts/audit-api-surface.ps1 \
-BranchName feature-system-text-json-v2 \
-AuditRunId live-serialization
```

**Requirements:** API and Elasticsearch must be running locally through Aspire.

### 2. Diff the Output

```bash
diff -r audit-output/main/ audit-output/feature-system-text-json-v2/ | head -100
```

Or for structured comparison:
```bash
# Compare specific test outputs
diff audit-output/main/events-post-snake-case/elastic.json \
audit-output/feature-system-text-json-v2/events-post-snake-case/elastic.json
```

### 3. Categorize Differences

Common difference categories:
| Category | Example | Severity |
|----------|---------|----------|
| Casing binding failure | `ReferenceId` in ExtensionData instead of property | CRITICAL |
| Date parsing expansion | `"2026-01-15"` → `"2026-01-15T00:00:00+00:00"` | MEDIUM |
| Numeric precision | `0` vs `0.0` | LOW |
| Empty collection omission | `"tags": []` omitted | LOW/EXPECTED |
| Character encoding | `&` vs `\u0026` | LOW |

### 4. Write Targeted Tests

For each difference found, write a **unit test** that reproduces it in isolation:

```csharp
// In tests/Exceptionless.Tests/Serializer/CasingCompatibilityTests.cs
[Theory]
[InlineData("reference_id")] // snake_case - should always work
[InlineData("ReferenceId")] // PascalCase - must also work
[InlineData("referenceId")] // camelCase - must also work
public void Deserialize_ReferenceId_MatchesAllCasings(string key)
{
string json = $$"""{"type": "error", "{{key}}": "test-ref-123"}""";
var ev = _serializer.Deserialize<PersistentEvent>(json);
Assert.Equal("test-ref-123", ev.ReferenceId);
}
```

### 5. Implement Fixes

Common fix patterns:
- **Multi-word property casing:** Add fallback in `IJsonOnDeserialized.OnDeserialized()` to check ExtensionData for alternate casings
- **Date-only string parsing:** Check for time separator ('T') before calling `TryGetDateTimeOffset` in `ObjectToInferredTypesConverter`
- **Naming policy mismatches:** Use `[JsonPropertyName]` attributes or TypeInfo modifiers

### 6. Re-run Audit

After fixes, run the audit into a new output directory (or the same branch directory — it overwrites):

```bash
pwsh .agents/skills/serialization-audit/scripts/audit-api-surface.ps1 -AuditRunId post-fixes
```

Compare again to verify differences are resolved.

## Key Files

| File | Purpose |
|------|---------|
| `tests/Exceptionless.Tests/Serializer/CasingCompatibilityTests.cs` | Unit tests for specific casing/format issues |
| `src/Exceptionless.Core/Serialization/ObjectToInferredTypesConverter.cs` | Type inference for untyped JSON values |
| `src/Exceptionless.Core/Serialization/JsonSerializerOptionsExtensions.cs` | STJ configuration (naming policy, converters) |
| `src/Exceptionless.Core/Models/Event.cs` | Event model with `OnDeserialized` fallback logic |
| `.agents/skills/serialization-audit/scripts/audit-api-surface.ps1` | Live localhost audit runner |
| `audit-output/` | Generated comparison files (gitignored) |

## Design Principles

1. **Backwards compatibility first:** Any payload that worked with Newtonsoft must still work with STJ
2. **Snake_case output, any-case input:** Serialize as snake_case, but accept PascalCase, camelCase, and snake_case on deserialization
3. **Preserve user data types:** Don't expand date-only strings to DateTimeOffset — users may store non-date strings that happen to look like dates
4. **Test the full pipeline:** Unit tests catch the bug, integration tests prove the fix works end-to-end
Loading
Loading