Skip to content

Commit

Permalink
Add tests verifying fast-path semantics for nullable structs (#61711)
Browse files Browse the repository at this point in the history
  • Loading branch information
layomia authored Nov 18, 2021
1 parent b11bda2 commit ecdb7d2
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public interface ITestContext
public JsonTypeInfo<StructWithCustomConverterPropertyFactory> StructWithCustomConverterPropertyFactory { get; }
public JsonTypeInfo<ClassWithBadCustomConverter> ClassWithBadCustomConverter { get; }
public JsonTypeInfo<StructWithBadCustomConverter> StructWithBadCustomConverter { get; }
public JsonTypeInfo<PersonStruct?> NullablePersonStruct { get; }
}

internal partial class JsonContext : JsonSerializerContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
[JsonSerializable(typeof(StructWithBadCustomConverter))]
[JsonSerializable(typeof(PersonStruct?))]
internal partial class MetadataAndSerializationContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Default;
Expand Down Expand Up @@ -85,6 +86,8 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.NotNull(MetadataAndSerializationContext.Default.StructWithCustomConverterPropertyFactory);
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.ClassWithBadCustomConverter);
Assert.Throws<InvalidOperationException>(() => MetadataAndSerializationContext.Default.StructWithBadCustomConverter);
Assert.Null(MetadataAndSerializationContext.Default.NullablePersonStruct.SerializeHandler);
Assert.NotNull(MetadataAndSerializationContext.Default.PersonStruct.SerializeHandler);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata)]
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Metadata)]
internal partial class MetadataWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
Expand Down Expand Up @@ -83,6 +84,8 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MetadataWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => MetadataWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.NullablePersonStruct.SerializeHandler);
Assert.Null(MetadataWithPerTypeAttributeContext.Default.PersonStruct.SerializeHandler);
}
}

Expand Down Expand Up @@ -120,6 +123,7 @@ public override void EnsureFastPathGeneratedAsExpected()
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
[JsonSerializable(typeof(StructWithBadCustomConverter))]
[JsonSerializable(typeof(PersonStruct?))]
internal partial class MetadataContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata;
Expand Down Expand Up @@ -187,6 +191,8 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MetadataContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.ClassWithBadCustomConverter.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => MetadataContext.Default.StructWithBadCustomConverter.SerializeHandler);
Assert.Null(MetadataContext.Default.NullablePersonStruct.SerializeHandler);
Assert.Null(MetadataContext.Default.PersonStruct.SerializeHandler);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
internal partial class MixedModeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization;
Expand Down Expand Up @@ -85,6 +86,8 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(MixedModeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.ClassWithBadCustomConverter.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => MixedModeContext.Default.StructWithBadCustomConverter.SerializeHandler);
Assert.Null(MixedModeContext.Default.NullablePersonStruct.SerializeHandler);
Assert.NotNull(MixedModeContext.Default.PersonStruct.SerializeHandler);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -864,5 +864,22 @@ public void PropertyOrdering()
string json = JsonSerializer.Serialize(obj, DefaultContext.MyTypeWithPropertyOrdering);
Assert.Equal("{\"C\":0,\"B\":0,\"A\":0}", json);
}

[Fact]
public virtual void NullableStruct()
{
PersonStruct? person = new()
{
FirstName = "Jane",
LastName = "Doe"
};

string json = JsonSerializer.Serialize(person, DefaultContext.NullablePersonStruct);
JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json);

person = JsonSerializer.Deserialize(json, DefaultContext.NullablePersonStruct);
Assert.Equal("Jane", person.Value.FirstName);
Assert.Equal("Doe", person.Value.LastName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace System.Text.Json.SourceGeneration.Tests
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory))]
[JsonSerializable(typeof(ClassWithBadCustomConverter))]
[JsonSerializable(typeof(StructWithBadCustomConverter))]
[JsonSerializable(typeof(PersonStruct?))]
internal partial class SerializationContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
Expand Down Expand Up @@ -78,6 +79,7 @@ internal partial class SerializationContext : JsonSerializerContext, ITestContex
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationWithPerTypeAttributeContext : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
Expand Down Expand Up @@ -117,6 +119,7 @@ internal partial class SerializationWithPerTypeAttributeContext : JsonSerializer
[JsonSerializable(typeof(StructWithCustomConverterPropertyFactory), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(ClassWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(StructWithBadCustomConverter), GenerationMode = JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(PersonStruct?), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class SerializationContextWithCamelCase : JsonSerializerContext, ITestContext
{
public JsonSourceGenerationMode JsonSourceGenerationMode => JsonSourceGenerationMode.Serialization;
Expand Down Expand Up @@ -164,6 +167,8 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(SerializationContext.Default.StructWithCustomConverterProperty.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.ClassWithBadCustomConverter.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => SerializationContext.Default.StructWithBadCustomConverter.SerializeHandler);
Assert.Null(SerializationContext.Default.NullablePersonStruct.SerializeHandler);
Assert.NotNull(SerializationContext.Default.PersonStruct.SerializeHandler);
}

[Fact]
Expand Down Expand Up @@ -431,6 +436,21 @@ public void OnSerializeCallbacks()
Assert.Equal("{\"MyProperty\":\"Before\"}", json);
Assert.Equal("After", obj.MyProperty);
}

[Fact]
public override void NullableStruct()
{
PersonStruct? person = new()
{
FirstName = "Jane",
LastName = "Doe"
};

string json = JsonSerializer.Serialize(person, DefaultContext.NullablePersonStruct);
JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json);

Assert.Throws<InvalidOperationException>(() => JsonSerializer.Deserialize(json, DefaultContext.NullablePersonStruct));
}
}

public sealed class SerializationWithPerTypeAttributeContextTests : SerializationContextTests
Expand Down Expand Up @@ -470,6 +490,8 @@ public override void EnsureFastPathGeneratedAsExpected()
Assert.Null(SerializationWithPerTypeAttributeContext.Default.StructWithCustomConverterPropertyFactory.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.ClassWithBadCustomConverter.SerializeHandler);
Assert.Throws<InvalidOperationException>(() => SerializationWithPerTypeAttributeContext.Default.StructWithBadCustomConverter.SerializeHandler);
Assert.Null(SerializationWithPerTypeAttributeContext.Default.NullablePersonStruct.SerializeHandler);
Assert.NotNull(SerializationWithPerTypeAttributeContext.Default.PersonStruct.SerializeHandler);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.IO;
using System.Text.Encodings.Web;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Xunit;

namespace System.Text.Json.SourceGeneration.Tests
Expand Down Expand Up @@ -123,5 +124,70 @@ public static void WriterIsFlushedAtRootCall()
Assert.Equal(18, writer.BytesCommitted);
Assert.Equal(0, writer.BytesPending);
}

[Fact]
public static void FastPathInvokedForNullableUnderlyingType()
{
PersonStruct? person = new()
{
FirstName = "Jane",
LastName = "Doe"
};

NullablePersonContext context = new();
Assert.False(context.FastPathCalled);
string json = JsonSerializer.Serialize(person, context.NullablePersonStruct);
Assert.True(context.FastPathCalled);
JsonTestHelper.AssertJsonEqual(@"{""FirstName"":""Jane"",""LastName"":""Doe""}", json);
}

internal partial class NullablePersonContext : JsonSerializerContext
{
private static JsonSerializerOptions s_options = new JsonSerializerOptions();

public bool FastPathCalled { get; private set; }

public NullablePersonContext() : base(s_options)
{
}

protected override JsonSerializerOptions? GeneratedSerializerOptions => s_options;

public JsonTypeInfo<PersonStruct?> NullablePersonStruct =>
JsonMetadataServices.CreateValueInfo<PersonStruct?>(s_options, JsonMetadataServices.GetNullableConverter(underlyingTypeInfo: PersonStruct));

public JsonTypeInfo<PersonStruct> PersonStruct
{
get
{
var objectInfo = new JsonObjectInfoValues<PersonStruct>()
{
ObjectCreator = static () => new PersonStruct(),
SerializeHandler = PersonStructSerializeHandler
};

return JsonMetadataServices.CreateObjectInfo(s_options, objectInfo);
}
}

private void PersonStructSerializeHandler(Utf8JsonWriter writer, PersonStruct value)
{
FastPathCalled = true;
writer.WriteStartObject();
writer.WriteString("FirstName", value.FirstName);
writer.WriteString("LastName", value.LastName);
writer.WriteEndObject();
}

public override JsonTypeInfo? GetTypeInfo(Type type)
{
if (type == typeof(PersonStruct))
{
return PersonStruct;
}

return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,10 @@ public class JsonMessage
}

internal struct MyStruct { }

public struct PersonStruct
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}

0 comments on commit ecdb7d2

Please sign in to comment.