Skip to content

Commit ecdb7d2

Browse files
authoredNov 18, 2021
Add tests verifying fast-path semantics for nullable structs (#61711)
1 parent b11bda2 commit ecdb7d2

8 files changed

+124
-0
lines changed
 

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

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public interface ITestContext
4646
public JsonTypeInfo<StructWithCustomConverterPropertyFactory> StructWithCustomConverterPropertyFactory { get; }
4747
public JsonTypeInfo<ClassWithBadCustomConverter> ClassWithBadCustomConverter { get; }
4848
public JsonTypeInfo<StructWithBadCustomConverter> StructWithBadCustomConverter { get; }
49+
public JsonTypeInfo<PersonStruct?> NullablePersonStruct { get; }
4950
}
5051

5152
internal partial class JsonContext : JsonSerializerContext

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

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests
4040
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
4141
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
4242
[JsonSerializable(typeof(StructWithBadCustomConverter))]
43+
[JsonSerializable(typeof(PersonStruct?))]
4344
internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
4445
{
4546
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Default;
@@ -85,6 +86,8 @@ public override void EnsureFastPathGeneratedAsExpected()
8586
Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterPropertyFactory);
8687
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter);
8788
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter);
89+
Assert.Null(MetadataAndSerializationContext.Default.NullablePersonStruct.SerializeHandler);
90+
Assert.NotNull(MetadataAndSerializationContext.Default.PersonStruct.SerializeHandler);
8891
}
8992
}
9093
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ namespace System.Text.Json.SourceGeneration.Tests
3939
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)]
4040
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
4141
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
42+
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Metadata)]
4243
internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
4344
{
4445
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
@@ -83,6 +84,8 @@ public override void EnsureFastPathGeneratedAsExpected()
8384
Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
8485
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.SerializeHandler);
8586
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.SerializeHandler);
87+
Assert.Null(MetadataWithPerTypeAttributeContext.Default.NullablePersonStruct.SerializeHandler);
88+
Assert.Null(MetadataWithPerTypeAttributeContext.Default.PersonStruct.SerializeHandler);
8689
}
8790
}
8891

@@ -120,6 +123,7 @@ public override void EnsureFastPathGeneratedAsExpected()
120123
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
121124
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
122125
[JsonSerializable(typeof(StructWithBadCustomConverter))]
126+
[JsonSerializable(typeof(PersonStruct?))]
123127
internal partial class MetadataContext : JsonSerializerContext, ITestContext
124128
{
125129
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
@@ -187,6 +191,8 @@ public override void EnsureFastPathGeneratedAsExpected()
187191
Assert.Null(MetadataContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
188192
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.ClassWithBadCustomConverter.SerializeHandler);
189193
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.StructWithBadCustomConverter.SerializeHandler);
194+
Assert.Null(MetadataContext.Default.NullablePersonStruct.SerializeHandler);
195+
Assert.Null(MetadataContext.Default.PersonStruct.SerializeHandler);
190196
}
191197

192198
[Fact]

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

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests
4040
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
4141
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
4242
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
43+
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
4344
internal partial class MixedModeContext : JsonSerializerContext, ITestContext
4445
{
4546
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization;
@@ -85,6 +86,8 @@ public override void EnsureFastPathGeneratedAsExpected()
8586
Assert.Null(MixedModeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
8687
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.ClassWithBadCustomConverter.SerializeHandler);
8788
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.StructWithBadCustomConverter.SerializeHandler);
89+
Assert.Null(MixedModeContext.Default.NullablePersonStruct.SerializeHandler);
90+
Assert.NotNull(MixedModeContext.Default.PersonStruct.SerializeHandler);
8891
}
8992

9093
[Fact]

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

+17
Original file line numberDiff line numberDiff line change
@@ -864,5 +864,22 @@ public void PropertyOrdering()
864864
string json = JsonSerializer.Serialize(obj, DefaultContext.MyTypeWithPropertyOrdering);
865865
Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json);
866866
}
867+
868+
[Fact]
869+
public virtual void NullableStruct()
870+
{
871+
PersonStruct? person = new()
872+
{
873+
FirstName = "Jane",
874+
LastName = "Doe"
875+
};
876+
877+
string json = JsonSerializer.Serialize(person, DefaultContext.NullablePersonStruct);
878+
JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json);
879+
880+
person = JsonSerializer.Deserialize(json, DefaultContext.NullablePersonStruct);
881+
Assert.Equal("Jane", person.Value.FirstName);
882+
Assert.Equal("Doe", person.Value.LastName);
883+
}
867884
}
868885
}

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

+22
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests
4040
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
4141
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
4242
[JsonSerializable(typeof(StructWithBadCustomConverter))]
43+
[JsonSerializable(typeof(PersonStruct?))]
4344
internal partial class SerializationContext : JsonSerializerContext, ITestContext
4445
{
4546
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
@@ -78,6 +79,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
7879
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
7980
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
8081
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
82+
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Serialization)]
8183
internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
8284
{
8385
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
@@ -117,6 +119,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
117119
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
118120
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
119121
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
122+
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Serialization)]
120123
internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
121124
{
122125
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
@@ -164,6 +167,8 @@ public override void EnsureFastPathGeneratedAsExpected()
164167
Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.SerializeHandler);
165168
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.ClassWithBadCustomConverter.SerializeHandler);
166169
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.StructWithBadCustomConverter.SerializeHandler);
170+
Assert.Null(SerializationContext.Default.NullablePersonStruct.SerializeHandler);
171+
Assert.NotNull(SerializationContext.Default.PersonStruct.SerializeHandler);
167172
}
168173

169174
[Fact]
@@ -431,6 +436,21 @@ public void OnSerializeCallbacks()
431436
Assert.Equal("{\"MyProperty\":\"Before\"}", json);
432437
Assert.Equal("After", obj.MyProperty);
433438
}
439+
440+
[Fact]
441+
public override void NullableStruct()
442+
{
443+
PersonStruct? person = new()
444+
{
445+
FirstName = "Jane",
446+
LastName = "Doe"
447+
};
448+
449+
string json = JsonSerializer.Serialize(person, DefaultContext.NullablePersonStruct);
450+
JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json);
451+
452+
Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize(json, DefaultContext.NullablePersonStruct));
453+
}
434454
}
435455

436456
public sealed class SerializationWithPerTypeAttributeContextTests : SerializationContextTests
@@ -470,6 +490,8 @@ public override void EnsureFastPathGeneratedAsExpected()
470490
Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
471491
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.SerializeHandler);
472492
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.SerializeHandler);
493+
Assert.Null(SerializationWithPerTypeAttributeContext.Default.NullablePersonStruct.SerializeHandler);
494+
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.PersonStruct.SerializeHandler);
473495
}
474496
}
475497
}

‎src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationLogicTests.cs

+66
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.IO;
66
using System.Text.Encodings.Web;
77
using System.Text.Json.Serialization;
8+
using System.Text.Json.Serialization.Metadata;
89
using Xunit;
910

1011
namespace System.Text.Json.SourceGeneration.Tests
@@ -123,5 +124,70 @@ public static void WriterIsFlushedAtRootCall()
123124
Assert.Equal(18, writer.BytesCommitted);
124125
Assert.Equal(0, writer.BytesPending);
125126
}
127+
128+
[Fact]
129+
public static void FastPathInvokedForNullableUnderlyingType()
130+
{
131+
PersonStruct? person = new()
132+
{
133+
FirstName = "Jane",
134+
LastName = "Doe"
135+
};
136+
137+
NullablePersonContext context = new();
138+
Assert.False(context.FastPathCalled);
139+
string json = JsonSerializer.Serialize(person, context.NullablePersonStruct);
140+
Assert.True(context.FastPathCalled);
141+
JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json);
142+
}
143+
144+
internal partial class NullablePersonContext : JsonSerializerContext
145+
{
146+
private static JsonSerializerOptions s_options = new JsonSerializerOptions();
147+
148+
public bool FastPathCalled { get; private set; }
149+
150+
public NullablePersonContext() : base(s_options)
151+
{
152+
}
153+
154+
protected override JsonSerializerOptions? GeneratedSerializerOptions => s_options;
155+
156+
public JsonTypeInfo<PersonStruct?> NullablePersonStruct =>
157+
JsonMetadataServices.CreateValueInfo<PersonStruct?>(s_options, JsonMetadataServices.GetNullableConverter(underlyingTypeInfo: PersonStruct));
158+
159+
public JsonTypeInfo<PersonStruct> PersonStruct
160+
{
161+
get
162+
{
163+
var objectInfo = new JsonObjectInfoValues<PersonStruct>()
164+
{
165+
ObjectCreator = static () => new PersonStruct(),
166+
SerializeHandler = PersonStructSerializeHandler
167+
};
168+
169+
return JsonMetadataServices.CreateObjectInfo(s_options, objectInfo);
170+
}
171+
}
172+
173+
private void PersonStructSerializeHandler(Utf8JsonWriter writer, PersonStruct value)
174+
{
175+
FastPathCalled = true;
176+
writer.WriteStartObject();
177+
writer.WriteString("FirstName", value.FirstName);
178+
writer.WriteString("LastName", value.LastName);
179+
writer.WriteEndObject();
180+
}
181+
182+
public override JsonTypeInfo? GetTypeInfo(Type type)
183+
{
184+
if (type == typeof(PersonStruct))
185+
{
186+
return PersonStruct;
187+
}
188+
189+
return null;
190+
}
191+
}
126192
}
127193
}

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

+6
Original file line numberDiff line numberDiff line change
@@ -151,4 +151,10 @@ public class JsonMessage
151151
}
152152

153153
internal struct MyStruct { }
154+
155+
public struct PersonStruct
156+
{
157+
public string FirstName { get; set; }
158+
public string LastName { get; set; }
159+
}
154160
}

0 commit comments

Comments
 (0)
Please sign in to comment.