Skip to content

Commit a9723f3

Browse files
Include properties on records for (de)serialization in source-gen (#63454)
Co-authored-by: Layomi Akinrinade <[email protected]>
1 parent 26b0a12 commit a9723f3

File tree

3 files changed

+251
-10
lines changed

3 files changed

+251
-10
lines changed

src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs

-6
Original file line numberDiff line numberDiff line change
@@ -396,12 +396,6 @@ public override PropertyInfo[] GetProperties(BindingFlags bindingAttr)
396396
{
397397
if (item is IPropertySymbol propertySymbol)
398398
{
399-
// Skip auto-generated properties on records.
400-
if (_typeSymbol.IsRecord && propertySymbol.DeclaringSyntaxReferences.Length == 0)
401-
{
402-
continue;
403-
}
404-
405399
// Skip if:
406400
if (
407401
// we want a static property and this is not static

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs

+53
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,59 @@ public partial class MyJsonContext : JsonSerializerContext
336336
return CreateCompilation(source);
337337
}
338338

339+
public static Compilation CreateReferencedLibRecordCompilation()
340+
{
341+
string source = @"
342+
using System.Text.Json.Serialization;
343+
344+
namespace ReferencedAssembly
345+
{
346+
public record LibRecord(int Id)
347+
{
348+
public string Address1 { get; set; }
349+
public string Address2 { get; set; }
350+
public string City { get; set; }
351+
public string State { get; set; }
352+
public string PostalCode { get; set; }
353+
public string Name { get; set; }
354+
[JsonInclude]
355+
public string PhoneNumber;
356+
[JsonInclude]
357+
public string Country;
358+
}
359+
}
360+
";
361+
362+
return CreateCompilation(source);
363+
}
364+
365+
public static Compilation CreateReferencedSimpleLibRecordCompilation()
366+
{
367+
string source = @"
368+
using System.Text.Json.Serialization;
369+
370+
namespace ReferencedAssembly
371+
{
372+
public record LibRecord
373+
{
374+
public int Id { get; set; }
375+
public string Address1 { get; set; }
376+
public string Address2 { get; set; }
377+
public string City { get; set; }
378+
public string State { get; set; }
379+
public string PostalCode { get; set; }
380+
public string Name { get; set; }
381+
[JsonInclude]
382+
public string PhoneNumber;
383+
[JsonInclude]
384+
public string Country;
385+
}
386+
}
387+
";
388+
389+
return CreateCompilation(source);
390+
}
391+
339392
internal static void CheckDiagnosticMessages(
340393
DiagnosticSeverity level,
341394
ImmutableArray<Diagnostic> diagnostics,

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs

+198-4
Original file line numberDiff line numberDiff line change
@@ -448,22 +448,216 @@ public void UsePrivates()
448448
CheckFieldsPropertiesMethods(myType, expectedFieldNames, expectedPropertyNames, expectedMethodNames);
449449
}
450450

451+
[Fact]
452+
public void Record()
453+
{
454+
// Compile the referenced assembly first.
455+
Compilation referencedCompilation = CompilationHelper.CreateReferencedLibRecordCompilation();
456+
457+
// Emit the image of the referenced assembly.
458+
byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation);
459+
460+
string source = @"
461+
using System.Text.Json.Serialization;
462+
463+
namespace HelloWorld
464+
{
465+
[JsonSerializable(typeof(AppRecord))]
466+
internal partial class JsonContext : JsonSerializerContext
467+
{
468+
}
469+
470+
public record AppRecord(int Id)
471+
{
472+
public string Address1 { get; set; }
473+
public string Address2 { get; set; }
474+
public string City { get; set; }
475+
public string State { get; set; }
476+
public string PostalCode { get; set; }
477+
public string Name { get; set; }
478+
[JsonInclude]
479+
public string PhoneNumber;
480+
[JsonInclude]
481+
public string Country;
482+
}
483+
}";
484+
485+
MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };
486+
487+
Compilation compilation = CompilationHelper.CreateCompilation(source);
488+
489+
JsonSourceGenerator generator = new JsonSourceGenerator();
490+
491+
Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);
492+
493+
// Make sure compilation was successful.
494+
CheckCompilationDiagnosticsErrors(generatorDiags);
495+
CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics());
496+
497+
Dictionary<string, Type> types = generator.GetSerializableTypes();
498+
499+
// Check base functionality of found types.
500+
Assert.Equal(1, types.Count);
501+
Type recordType = types["HelloWorld.AppRecord"];
502+
Assert.Equal("HelloWorld.AppRecord", recordType.FullName);
503+
504+
// Check for received fields, properties and methods for NotMyType.
505+
string[] expectedFieldsNames = { "Country", "PhoneNumber" };
506+
string[] expectedPropertyNames = { "Address1", "Address2", "City", "Id", "Name", "PostalCode", "State" };
507+
CheckFieldsPropertiesMethods(recordType, expectedFieldsNames, expectedPropertyNames);
508+
509+
Assert.Equal(1, recordType.GetConstructors().Length);
510+
}
511+
512+
[Fact]
513+
public void RecordInExternalAssembly()
514+
{
515+
// Compile the referenced assembly first.
516+
Compilation referencedCompilation = CompilationHelper.CreateReferencedLibRecordCompilation();
517+
518+
// Emit the image of the referenced assembly.
519+
byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation);
520+
521+
string source = @"
522+
using System.Text.Json.Serialization;
523+
using ReferencedAssembly;
524+
525+
namespace HelloWorld
526+
{
527+
[JsonSerializable(typeof(LibRecord))]
528+
internal partial class JsonContext : JsonSerializerContext
529+
{
530+
}
531+
}";
532+
533+
MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };
534+
535+
Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences);
536+
537+
JsonSourceGenerator generator = new JsonSourceGenerator();
538+
539+
Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);
540+
541+
// Make sure compilation was successful.
542+
CheckCompilationDiagnosticsErrors(generatorDiags);
543+
CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics());
544+
545+
Dictionary<string, Type> types = generator.GetSerializableTypes();
546+
547+
Assert.Equal(1, types.Count);
548+
Type recordType = types["ReferencedAssembly.LibRecord"];
549+
Assert.Equal("ReferencedAssembly.LibRecord", recordType.FullName);
550+
551+
string[] expectedFieldsNames = { "Country", "PhoneNumber" };
552+
string[] expectedPropertyNames = { "Address1", "Address2", "City", "Id", "Name", "PostalCode", "State" };
553+
CheckFieldsPropertiesMethods(recordType, expectedFieldsNames, expectedPropertyNames);
554+
555+
Assert.Equal(1, recordType.GetConstructors().Length);
556+
}
557+
558+
[Fact]
559+
public void RecordDerivedFromRecordInExternalAssembly()
560+
{
561+
// Compile the referenced assembly first.
562+
Compilation referencedCompilation = CompilationHelper.CreateReferencedSimpleLibRecordCompilation();
563+
564+
// Emit the image of the referenced assembly.
565+
byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation);
566+
567+
string source = @"
568+
using System.Text.Json.Serialization;
569+
using ReferencedAssembly;
570+
571+
namespace HelloWorld
572+
{
573+
[JsonSerializable(typeof(AppRecord))]
574+
internal partial class JsonContext : JsonSerializerContext
575+
{
576+
}
577+
578+
internal record AppRecord : LibRecord
579+
{
580+
public string ExtraData { get; set; }
581+
}
582+
}";
583+
584+
MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) };
585+
586+
Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences);
587+
588+
JsonSourceGenerator generator = new JsonSourceGenerator();
589+
590+
Compilation newCompilation = CompilationHelper.RunGenerators(compilation, out ImmutableArray<Diagnostic> generatorDiags, generator);
591+
592+
// Make sure compilation was successful.
593+
CheckCompilationDiagnosticsErrors(generatorDiags);
594+
CheckCompilationDiagnosticsErrors(newCompilation.GetDiagnostics());
595+
596+
Dictionary<string, Type> types = generator.GetSerializableTypes();
597+
598+
Assert.Equal(1, types.Count);
599+
Type recordType = types["HelloWorld.AppRecord"];
600+
Assert.Equal("HelloWorld.AppRecord", recordType.FullName);
601+
602+
string[] expectedFieldsNames = { "Country", "PhoneNumber" };
603+
string[] expectedPropertyNames = { "Address1", "Address2", "City", "ExtraData", "Id", "Name", "PostalCode", "State" };
604+
CheckFieldsPropertiesMethods(recordType, expectedFieldsNames, expectedPropertyNames, inspectBaseTypes: true);
605+
606+
Assert.Equal(1, recordType.GetConstructors().Length);
607+
}
608+
451609
private void CheckCompilationDiagnosticsErrors(ImmutableArray<Diagnostic> diagnostics)
452610
{
453611
Assert.Empty(diagnostics.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error));
454612
}
455613

456-
private void CheckFieldsPropertiesMethods(Type type, string[] expectedFields, string[] expectedProperties, string[] expectedMethods)
614+
private void CheckFieldsPropertiesMethods(
615+
Type type,
616+
string[] expectedFields,
617+
string[] expectedProperties,
618+
string[] expectedMethods = null,
619+
bool inspectBaseTypes = false)
457620
{
458621
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
459622

460-
string[] receivedFields = type.GetFields(bindingFlags).Select(field => field.Name).OrderBy(s => s).ToArray();
461-
string[] receivedProperties = type.GetProperties(bindingFlags).Select(property => property.Name).OrderBy(s => s).ToArray();
623+
string[] receivedFields;
624+
string[] receivedProperties;
625+
626+
if (!inspectBaseTypes)
627+
{
628+
receivedFields = type.GetFields(bindingFlags).Select(field => field.Name).OrderBy(s => s).ToArray();
629+
receivedProperties = type.GetProperties(bindingFlags).Select(property => property.Name).OrderBy(s => s).ToArray();
630+
}
631+
else
632+
{
633+
List<string> fields = new List<string>();
634+
List<string> props = new List<string>();
635+
636+
Type currentType = type;
637+
while (currentType != null)
638+
{
639+
fields.AddRange(currentType.GetFields(bindingFlags).Select(property => property.Name).OrderBy(s => s).ToArray());
640+
props.AddRange(currentType.GetProperties(bindingFlags).Select(property => property.Name).OrderBy(s => s).ToArray());
641+
currentType = currentType.BaseType;
642+
}
643+
644+
receivedFields = fields.ToArray();
645+
receivedProperties = props.ToArray();
646+
}
647+
462648
string[] receivedMethods = type.GetMethods().Select(method => method.Name).OrderBy(s => s).ToArray();
463649

650+
Array.Sort(receivedFields);
651+
Array.Sort(receivedProperties);
652+
Array.Sort(receivedMethods);
653+
464654
Assert.Equal(expectedFields, receivedFields);
465655
Assert.Equal(expectedProperties, receivedProperties);
466-
Assert.Equal(expectedMethods, receivedMethods);
656+
657+
if (expectedMethods != null)
658+
{
659+
Assert.Equal(expectedMethods, receivedMethods);
660+
}
467661
}
468662

469663
// TODO: add test guarding against (de)serializing static classes.

0 commit comments

Comments
 (0)