Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests verifying fast-path semantics for nullable structs #61711

Merged
merged 1 commit into from
Nov 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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; }
}
}