Skip to content

Commit ea48ccc

Browse files
Disable fast path serialization for types with properties using custom converters. (#58571)
Co-authored-by: Eirik Tsarpalis <eirik.tsarpalis@gmail.com>
1 parent 69b137a commit ea48ccc

File tree

9 files changed

+116
-5
lines changed

9 files changed

+116
-5
lines changed

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,7 @@ private string GenerateFastPathFuncForObject(TypeGenerationSpec typeGenSpec)
792792
out Dictionary<string, PropertyGenerationSpec>? serializableProperties,
793793
out bool castingRequiredForProps))
794794
{
795-
string exceptionMessage = @$"""Invalid serializable-property configuration specified for type '{typeRef}'. For more information, use 'JsonSourceGenerationMode.Serialization'.""";
795+
string exceptionMessage = @$"""Invalid serializable-property configuration specified for type '{typeRef}'. For more information, see 'JsonSourceGenerationMode.Serialization'.""";
796796

797797
return GenerateFastPathFuncForType(
798798
serializeMethodName,

src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ private bool FastPathIsSupported()
217217
{
218218
if (property.TypeGenerationSpec.Type.IsObjectType() ||
219219
property.NumberHandling == JsonNumberHandling.AllowNamedFloatingPointLiterals ||
220-
property.NumberHandling == JsonNumberHandling.WriteAsString)
220+
property.NumberHandling == JsonNumberHandling.WriteAsString ||
221+
property.ConverterInstantiationLogic is not null)
221222
{
222223
return false;
223224
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace System.Text.Json.SourceGeneration.Tests
99
{
1010
public interface ITestContext
1111
{
12+
public JsonSourceGenerationMode JsonSourceGenerationMode { get; }
13+
1214
public JsonTypeInfo<Location> Location { get; }
1315
public JsonTypeInfo<NumberTypes> NumberTypes { get; }
1416
public JsonTypeInfo<RepeatedTypes.Location> RepeatedLocation { get; }
@@ -31,6 +33,8 @@ public interface ITestContext
3133
public JsonTypeInfo<RealWorldContextTests.ClassWithEnumAndNullable> ClassWithEnumAndNullable { get; }
3234
public JsonTypeInfo<ClassWithCustomConverter> ClassWithCustomConverter { get; }
3335
public JsonTypeInfo<StructWithCustomConverter> StructWithCustomConverter { get; }
36+
public JsonTypeInfo<ClassWithCustomConverterProperty> ClassWithCustomConverterProperty { get; }
37+
public JsonTypeInfo<StructWithCustomConverterProperty> StructWithCustomConverterProperty { get; }
3438
public JsonTypeInfo<ClassWithBadCustomConverter> ClassWithBadCustomConverter { get; }
3539
public JsonTypeInfo<StructWithBadCustomConverter> StructWithBadCustomConverter { get; }
3640
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ namespace System.Text.Json.SourceGeneration.Tests
2929
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
3030
[JsonSerializable(typeof(ClassWithCustomConverter))]
3131
[JsonSerializable(typeof(StructWithCustomConverter))]
32+
[JsonSerializable(typeof(ClassWithCustomConverterProperty))]
33+
[JsonSerializable(typeof(StructWithCustomConverterProperty))]
3234
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
3335
[JsonSerializable(typeof(StructWithBadCustomConverter))]
3436
internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
3537
{
38+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Default;
3639
}
3740

3841
public sealed class MetadataAndSerializationContextTests : RealWorldContextTests
@@ -64,6 +67,8 @@ public override void EnsureFastPathGeneratedAsExpected()
6467
Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithEnumAndNullable.Serialize);
6568
Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverter);
6669
Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverter);
70+
Assert.NotNull(MetadataAndSerializationContext.Default.ClassWithCustomConverterProperty);
71+
Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterProperty);
6772
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter);
6873
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter);
6974
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataContextTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ namespace System.Text.Json.SourceGeneration.Tests
3030
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
3131
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
3232
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
33+
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)]
34+
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata)]
3335
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
3436
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
3537
internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
3638
{
39+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
3740
}
3841

3942
public sealed class MetadataWithPerTypeAttributeContextTests : RealWorldContextTests
@@ -91,10 +94,13 @@ public override void EnsureFastPathGeneratedAsExpected()
9194
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
9295
[JsonSerializable(typeof(ClassWithCustomConverter))]
9396
[JsonSerializable(typeof(StructWithCustomConverter))]
97+
[JsonSerializable(typeof(ClassWithCustomConverterProperty))]
98+
[JsonSerializable(typeof(StructWithCustomConverterProperty))]
9499
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
95100
[JsonSerializable(typeof(StructWithBadCustomConverter))]
96101
internal partial class MetadataContext : JsonSerializerContext, ITestContext
97102
{
103+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
98104
}
99105

100106
public sealed class MetadataContextTests : RealWorldContextTests
@@ -126,6 +132,8 @@ public override void EnsureFastPathGeneratedAsExpected()
126132
Assert.Null(MetadataContext.Default.ClassWithEnumAndNullable.Serialize);
127133
Assert.Null(MetadataContext.Default.ClassWithCustomConverter.Serialize);
128134
Assert.Null(MetadataContext.Default.StructWithCustomConverter.Serialize);
135+
Assert.Null(MetadataContext.Default.ClassWithCustomConverterProperty.Serialize);
136+
Assert.Null(MetadataContext.Default.StructWithCustomConverterProperty.Serialize);
129137
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.ClassWithBadCustomConverter.Serialize);
130138
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.StructWithBadCustomConverter.Serialize);
131139
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ namespace System.Text.Json.SourceGeneration.Tests
2828
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
2929
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
3030
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
31+
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
32+
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
3133
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
3234
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
3335
internal partial class MixedModeContext : JsonSerializerContext, ITestContext
3436
{
37+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization;
3538
}
3639

3740
public sealed class MixedModeContextTests : RealWorldContextTests
@@ -62,6 +65,8 @@ public override void EnsureFastPathGeneratedAsExpected()
6265
Assert.NotNull(MixedModeContext.Default.ClassWithEnumAndNullable.Serialize);
6366
Assert.Null(MixedModeContext.Default.ClassWithCustomConverter.Serialize);
6467
Assert.Null(MixedModeContext.Default.StructWithCustomConverter.Serialize);
68+
Assert.Null(MixedModeContext.Default.ClassWithCustomConverterProperty.Serialize);
69+
Assert.Null(MixedModeContext.Default.StructWithCustomConverterProperty.Serialize);
6570
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.ClassWithBadCustomConverter.Serialize);
6671
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.StructWithBadCustomConverter.Serialize);
6772
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/RealWorldContextTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,60 @@ public virtual void RoundTripWithCustomConverter_Struct()
144144
Assert.Equal(42, obj.MyInt);
145145
}
146146

147+
[Fact]
148+
public virtual void RoundtripWithCustomConverterProperty_Class()
149+
{
150+
const string ExpectedJson = "{\"Property\":42}";
151+
152+
ClassWithCustomConverterProperty obj = new()
153+
{
154+
Property = new ClassWithCustomConverterProperty.NestedPoco { Value = 42 }
155+
};
156+
157+
// Types with properties in custom converters do not support fast path serialization.
158+
Assert.True(DefaultContext.ClassWithCustomConverterProperty.Serialize is null);
159+
160+
if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
161+
{
162+
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterProperty));
163+
}
164+
else
165+
{
166+
string json = JsonSerializer.Serialize(obj, DefaultContext.ClassWithCustomConverterProperty);
167+
Assert.Equal(ExpectedJson, json);
168+
}
169+
170+
obj = JsonSerializer.Deserialize<ClassWithCustomConverterProperty>(ExpectedJson);
171+
Assert.Equal(42, obj.Property.Value);
172+
}
173+
174+
[Fact]
175+
public virtual void RoundtripWithCustomConverterProperty_Struct()
176+
{
177+
const string ExpectedJson = "{\"Property\":42}";
178+
179+
StructWithCustomConverterProperty obj = new()
180+
{
181+
Property = new ClassWithCustomConverterProperty.NestedPoco { Value = 42 }
182+
};
183+
184+
// Types with properties in custom converters do not support fast path serialization.
185+
Assert.True(DefaultContext.StructWithCustomConverterProperty.Serialize is null);
186+
187+
if (DefaultContext.JsonSourceGenerationMode == JsonSourceGenerationMode.Serialization)
188+
{
189+
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterProperty));
190+
}
191+
else
192+
{
193+
string json = JsonSerializer.Serialize(obj, DefaultContext.StructWithCustomConverterProperty);
194+
Assert.Equal(ExpectedJson, json);
195+
}
196+
197+
obj = JsonSerializer.Deserialize<StructWithCustomConverterProperty>(ExpectedJson);
198+
Assert.Equal(42, obj.Property.Value);
199+
}
200+
147201
[Fact]
148202
public virtual void BadCustomConverter_Class()
149203
{

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@ namespace System.Text.Json.SourceGeneration.Tests
2929
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable))]
3030
[JsonSerializable(typeof(ClassWithCustomConverter))]
3131
[JsonSerializable(typeof(StructWithCustomConverter))]
32+
[JsonSerializable(typeof(ClassWithCustomConverterProperty))]
33+
[JsonSerializable(typeof(StructWithCustomConverterProperty))]
3234
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
3335
[JsonSerializable(typeof(StructWithBadCustomConverter))]
3436
internal partial class SerializationContext : JsonSerializerContext, ITestContext
3537
{
38+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
3639
}
3740

3841
[JsonSerializable(typeof(Location), GenerationMode = JsonSourceGenerationMode.Serialization)]
@@ -57,12 +60,13 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
5760
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)]
5861
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
5962
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
60-
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
61-
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
63+
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
64+
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
6265
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
6366
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
6467
internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
6568
{
69+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
6670
}
6771

6872
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
@@ -88,10 +92,13 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
8892
[JsonSerializable(typeof(RealWorldContextTests.ClassWithEnumAndNullable), GenerationMode = JsonSourceGenerationMode.Serialization)]
8993
[JsonSerializable(typeof(ClassWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
9094
[JsonSerializable(typeof(StructWithCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
95+
[JsonSerializable(typeof(ClassWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
96+
[JsonSerializable(typeof(StructWithCustomConverterProperty), GenerationMode = JsonSourceGenerationMode.Serialization)]
9197
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
9298
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
9399
internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
94100
{
101+
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
95102
}
96103

97104
public class SerializationContextTests : RealWorldContextTests
@@ -128,6 +135,8 @@ public override void EnsureFastPathGeneratedAsExpected()
128135
Assert.NotNull(SerializationContext.Default.ClassWithEnumAndNullable.Serialize);
129136
Assert.Null(SerializationContext.Default.ClassWithCustomConverter.Serialize);
130137
Assert.Null(SerializationContext.Default.StructWithCustomConverter.Serialize);
138+
Assert.Null(SerializationContext.Default.ClassWithCustomConverterProperty.Serialize);
139+
Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.Serialize);
131140
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.ClassWithBadCustomConverter.Serialize);
132141
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.StructWithBadCustomConverter.Serialize);
133142
}
@@ -390,6 +399,8 @@ public override void EnsureFastPathGeneratedAsExpected()
390399
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.ClassWithEnumAndNullable.Serialize);
391400
Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverter.Serialize);
392401
Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverter.Serialize);
402+
Assert.Null(SerializationWithPerTypeAttributeContext.Default.ClassWithCustomConverterProperty.Serialize);
403+
Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterProperty.Serialize);
393404
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.Serialize);
394405
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.Serialize);
395406
}

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,31 @@ public class ClassWithCustomConverter
199199
public int MyInt { get; set; }
200200
}
201201

202+
public class ClassWithCustomConverterProperty
203+
{
204+
[JsonConverter(typeof(NestedPocoCustomConverter))]
205+
public NestedPoco Property { get; set; }
206+
207+
public class NestedPoco
208+
{
209+
public int Value { get; set; }
210+
}
211+
212+
public class NestedPocoCustomConverter : JsonConverter<NestedPoco>
213+
{
214+
public override NestedPoco? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new NestedPoco { Value = reader.GetInt32() };
215+
public override void Write(Utf8JsonWriter writer, NestedPoco value, JsonSerializerOptions options) => writer.WriteNumberValue(value.Value);
216+
}
217+
}
218+
219+
public struct StructWithCustomConverterProperty
220+
{
221+
[JsonConverter(typeof(ClassWithCustomConverterProperty.NestedPocoCustomConverter))]
222+
public ClassWithCustomConverterProperty.NestedPoco Property { get; set; }
223+
}
224+
202225
[JsonConverter(typeof(CustomConverterForStruct))] // Invalid
203-
public struct ClassWithBadCustomConverter
226+
public class ClassWithBadCustomConverter
204227
{
205228
public int MyInt { get; set; }
206229
}

0 commit comments

Comments
 (0)