|
| 1 | +--- |
| 2 | +title: "Breaking change: System.Text.Json metadata reader now unescapes metadata property names" |
| 3 | +description: System.Text.Json now unescapes metadata property names, affecting reference preservation and metadata property validation. |
| 4 | +ms.date: 2/13/2025 |
| 5 | +ai-usage: ai-assisted |
| 6 | +ms.custom: https://github.com/dotnet/docs/issues/44748 |
| 7 | +--- |
| 8 | + |
| 9 | +# System.Text.Json metadata reader now unescapes metadata property names |
| 10 | + |
| 11 | +The <xref:System.Text.Json?displayProperty=fullName> library has been updated to unescape metadata property names. This change affects how JSON documents are interpreted in the context of reference preservation, polymorphism, and metadata property validation. |
| 12 | + |
| 13 | +## Version introduced |
| 14 | + |
| 15 | +.NET 9 |
| 16 | + |
| 17 | +## Previous behavior |
| 18 | + |
| 19 | +Previously, <xref:System.Text.Json?displayProperty=fullName> didn't unescape metadata property names. This would lead to invalid property names being accepted, which could bypass metadata property validation. |
| 20 | + |
| 21 | +For example, the following code would succeed in the first call but throw an exception in the second call: |
| 22 | + |
| 23 | +```csharp |
| 24 | +JsonSerializerOptions options = new() { ReferenceHandler = ReferenceHandler.Preserve }; |
| 25 | +JsonSerializer.Deserialize<MyPoco>("""{"\u0024invalid" : 42 }""", options); |
| 26 | +JsonSerializer.Deserialize<MyPoco>("""{"$invalid" : 42 }""", options); |
| 27 | + |
| 28 | +record MyPoco; |
| 29 | +``` |
| 30 | + |
| 31 | +The unescaping behavior could also cause polymorphism issues when roundtripping metadata properties whose names require escaping, as seen here: |
| 32 | + |
| 33 | +```csharp |
| 34 | +string json = JsonSerializer.Serialize<Base>(new Derived()); |
| 35 | +Console.WriteLine(json); // {"categor\u00EDa":"derived"} |
| 36 | +Console.WriteLine(JsonSerializer.Deserialize<Base>(json) is Derived); // False |
| 37 | +
|
| 38 | +[JsonPolymorphic(TypeDiscriminatorPropertyName = "categoría")] |
| 39 | +[JsonDerivedType(typeof(Derived), "derived")] |
| 40 | +public record Base; |
| 41 | +public record Derived : Base; |
| 42 | +``` |
| 43 | + |
| 44 | +## New behavior |
| 45 | + |
| 46 | +<xref:System.Text.Json?displayProperty=fullName> now unescapes metadata property names. This new behavior means that the line `Console.WriteLine(JsonSerializer.Deserialize<Base>(json) is Derived);` from the polymorphic deserialization example now returns `true`, and that invalid property names correctly fail to deserialize with the following exception: |
| 47 | + |
| 48 | +```output |
| 49 | +Unhandled exception. System.Text.Json.JsonException: Properties that start with '$' are not allowed in types that support metadata. |
| 50 | +``` |
| 51 | + |
| 52 | +## Type of breaking change |
| 53 | + |
| 54 | +This change is a [behavioral change](../../categories.md#behavioral-change). |
| 55 | + |
| 56 | +## Reason for change |
| 57 | + |
| 58 | +The change improves correctness and reliability by ensuring that metadata property names are properly unescaped, preventing the bypass of metadata property validation. For more details, see the [reported issue](https://github.com/dotnet/runtime/issues/112288). |
| 59 | + |
| 60 | +## Recommended action |
| 61 | + |
| 62 | +Avoid using escaping to bypass metadata property validation. Instead, pick property names that don't conflict with metadata properties. |
| 63 | + |
| 64 | +## Affected APIs |
| 65 | + |
| 66 | +* <xref:System.Text.Json?displayProperty=fullName> |
| 67 | +* <xref:System.Text.Json.JsonSerializer.Deserialize*?displayProperty=fullName> |
0 commit comments