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 ReferenceHandler to JsonSourceGenerationOptions #109174

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
26 changes: 26 additions & 0 deletions src/libraries/System.Text.Json/Common/JsonKnownReferenceHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Text.Json.Serialization
{
/// <summary>
/// The <see cref="ReferenceHandler"/> to be used at run time.
/// </summary>
public enum JsonKnownReferenceHandler
{
/// <summary>
/// Specifies that circular references should throw exceptions.
/// </summary>
Unspecified = 0,

/// <summary>
/// Specifies that the built-in <see cref="ReferenceHandler.Preserve"/> be used to handle references.
/// </summary>
Preserve = 1,

/// <summary>
/// Specifies that the built-in <see cref="ReferenceHandler.IgnoreCycles"/> be used to ignore cyclic references.
/// </summary>
IgnoreCycles = 2,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ public JsonSourceGenerationOptionsAttribute(JsonSerializerDefaults defaults)
/// </summary>
public JsonCommentHandling ReadCommentHandling { get; set; }

/// <summary>
/// Specifies the default value of <see cref="JsonSerializerOptions.ReferenceHandler"/> when set.
/// </summary>
public JsonKnownReferenceHandler ReferenceHandler { get; set; }

/// <summary>
/// Specifies the default value of <see cref="JsonSerializerOptions.RespectNullableAnnotations"/> when set.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ private sealed partial class Emitter
private const string JsonPropertyInfoValuesTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonPropertyInfoValues";
private const string JsonTypeInfoTypeRef = "global::System.Text.Json.Serialization.Metadata.JsonTypeInfo";
private const string JsonTypeInfoResolverTypeRef = "global::System.Text.Json.Serialization.Metadata.IJsonTypeInfoResolver";
private const string ReferenceHandlerTypeRef = "global::System.Text.Json.Serialization.ReferenceHandler";
private const string EmptyTypeArray = "global::System.Array.Empty<global::System.Type>()";

/// <summary>
Expand Down Expand Up @@ -1246,6 +1247,9 @@ private static void GetLogicForDefaultSerializerOptionsInit(SourceGenerationOpti
if (optionsSpec.ReadCommentHandling is JsonCommentHandling readCommentHandling)
writer.WriteLine($"ReadCommentHandling = {FormatCommentHandling(readCommentHandling)},");

if (optionsSpec.ReferenceHandler is JsonKnownReferenceHandler referenceHandler)
writer.WriteLine($"ReferenceHandler = {FormatReferenceHandler(referenceHandler)},");

if (optionsSpec.UnknownTypeHandling is JsonUnknownTypeHandling unknownTypeHandling)
writer.WriteLine($"UnknownTypeHandling = {FormatUnknownTypeHandling(unknownTypeHandling)},");

Expand Down Expand Up @@ -1433,6 +1437,9 @@ private static string FormatIgnoreCondition(JsonIgnoreCondition ignoreCondition)
private static string FormatJsonSerializerDefaults(JsonSerializerDefaults defaults)
=> SourceGeneratorHelpers.FormatEnumLiteral(JsonSerializerDefaultsTypeRef, defaults);

private static string FormatReferenceHandler(JsonKnownReferenceHandler referenceHandler)
=> SourceGeneratorHelpers.FormatEnumLiteral(ReferenceHandlerTypeRef, referenceHandler);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not saying that this wouldn't work, but ReferenceHandler isn't technically an enum. It's a class with static properties, so perhaps different concerns apply to formatting it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of the enum didn't map to a default reference handler in the first iteration of this PR. Updated the PR to handle this enum -> reference handler object conversion. Note that for the naming policy, we actually have the policy classes in the Common folder so nameof works there. I tried to see if I could also move ReferenceHandler to Common but it has a couple of dependencies so I just left it as it is instead of adding a bunch of types to Common. I just hardcoded the property names for this PR.


private static string GetCreateValueInfoMethodRef(string typeCompilableName) => $"{CreateValueInfoMethodName}<{typeCompilableName}>";

private static string FormatBoolLiteral(bool value) => value ? "true" : "false";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
bool? propertyNameCaseInsensitive = null;
JsonKnownNamingPolicy? propertyNamingPolicy = null;
JsonCommentHandling? readCommentHandling = null;
JsonKnownReferenceHandler? referenceHandler = null;
JsonUnknownTypeHandling? unknownTypeHandling = null;
JsonUnmappedMemberHandling? unmappedMemberHandling = null;
bool? useStringEnumConverter = null;
Expand Down Expand Up @@ -379,6 +380,10 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
readCommentHandling = (JsonCommentHandling)namedArg.Value.Value!;
break;

case nameof(JsonSourceGenerationOptionsAttribute.ReferenceHandler):
referenceHandler = (JsonKnownReferenceHandler)namedArg.Value.Value!;
break;

case nameof(JsonSourceGenerationOptionsAttribute.UnknownTypeHandling):
unknownTypeHandling = (JsonUnknownTypeHandling)namedArg.Value.Value!;
break;
Expand Down Expand Up @@ -434,6 +439,7 @@ private SourceGenerationOptionsSpec ParseJsonSourceGenerationOptionsAttribute(IN
PropertyNameCaseInsensitive = propertyNameCaseInsensitive,
PropertyNamingPolicy = propertyNamingPolicy,
ReadCommentHandling = readCommentHandling,
ReferenceHandler = referenceHandler,
UnknownTypeHandling = unknownTypeHandling,
UnmappedMemberHandling = unmappedMemberHandling,
UseStringEnumConverter = useStringEnumConverter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public sealed record SourceGenerationOptionsSpec

public required JsonCommentHandling? ReadCommentHandling { get; init; }

public required JsonKnownReferenceHandler? ReferenceHandler { get; init; }

public required JsonUnknownTypeHandling? UnknownTypeHandling { get; init; }

public required JsonUnmappedMemberHandling? UnmappedMemberHandling { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Compile Include="..\Common\JsonKebabCaseLowerNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKebabCaseLowerNamingPolicy.cs" />
<Compile Include="..\Common\JsonKebabCaseUpperNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKebabCaseUpperNamingPolicy.cs" />
<Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
<Compile Include="..\Common\JsonKnownReferenceHandler.cs" Link="Common\System\Text\Json\Serialization\JsonKnownReferenceHandler.cs" />
<Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
<Compile Include="..\Common\JsonObjectCreationHandling.cs" Link="Common\System\Text\Json\Serialization\JsonObjectCreationHandling.cs" />
<Compile Include="..\Common\JsonSeparatorNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSeparatorNamingPolicy.cs" />
Expand Down
7 changes: 7 additions & 0 deletions src/libraries/System.Text.Json/ref/System.Text.Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,12 @@ public enum JsonKnownNamingPolicy
KebabCaseLower = 4,
KebabCaseUpper = 5,
}
public enum JsonKnownReferenceHandler
{
Unspecified = 0,
Preserve = 1,
IgnoreCycles = 2,
}
public sealed partial class JsonNumberEnumConverter<TEnum> : System.Text.Json.Serialization.JsonConverterFactory where TEnum : struct, System.Enum
{
public JsonNumberEnumConverter() { }
Expand Down Expand Up @@ -1143,6 +1149,7 @@ public JsonSourceGenerationOptionsAttribute(System.Text.Json.JsonSerializerDefau
public bool PropertyNameCaseInsensitive { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonKnownNamingPolicy PropertyNamingPolicy { get { throw null; } set { } }
public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonKnownReferenceHandler ReferenceHandler { get { throw null; } set { } }
public bool RespectNullableAnnotations { get { throw null; } set { } }
public bool RespectRequiredConstructorParameters { get { throw null; } set { } }
public System.Text.Json.Serialization.JsonUnknownTypeHandling UnknownTypeHandling { get { throw null; } set { } }
Expand Down
3 changes: 2 additions & 1 deletion src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="..\Common\JsonKebabCaseLowerNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKebabCaseLowerNamingPolicy.cs" />
<Compile Include="..\Common\JsonKebabCaseUpperNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKebabCaseUpperNamingPolicy.cs" />
<Compile Include="..\Common\JsonKnownNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonKnownNamingPolicy.cs" />
<Compile Include="..\Common\JsonKnownReferenceHandler.cs" Link="Common\System\Text\Json\Serialization\JsonKnownReferenceHandler.cs" />
<Compile Include="..\Common\JsonNumberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonNumberHandling.cs" />
<Compile Include="..\Common\JsonObjectCreationHandling.cs" Link="Common\System\Text\Json\Serialization\JsonObjectCreationHandling.cs" />
<Compile Include="..\Common\JsonUnmappedMemberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnmappedMemberHandling.cs" />
<Compile Include="..\Common\JsonSeparatorNamingPolicy.cs" Link="Common\System\Text\Json\Serialization\JsonSeparatorNamingPolicy.cs" />
<Compile Include="..\Common\JsonSerializableAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSerializableAttribute.cs" />
<Compile Include="..\Common\JsonSerializerDefaults.cs" Link="Common\System\Text\Json\Serialization\JsonSerializerDefaults.cs" />
Expand All @@ -44,6 +44,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
<Compile Include="..\Common\JsonSourceGenerationMode.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationMode.cs" />
<Compile Include="..\Common\JsonSourceGenerationOptionsAttribute.cs" Link="Common\System\Text\Json\Serialization\JsonSourceGenerationOptionsAttribute.cs" />
<Compile Include="..\Common\JsonUnknownTypeHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnknownTypeHandling.cs" />
<Compile Include="..\Common\JsonUnmappedMemberHandling.cs" Link="Common\System\Text\Json\Serialization\JsonUnmappedMemberHandling.cs" />
<Compile Include="..\Common\ReflectionExtensions.cs" Link="Common\System\Text\Json\Serialization\ReflectionExtensions.cs" />
<Compile Include="..\Common\ThrowHelper.cs" Link="Common\System\Text\Json\ThrowHelper.cs" />
<Compile Include="System\Runtime\InteropServices\JsonMarshal.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace System.Text.Json.Serialization
{
internal enum ReferenceHandlingStrategy
PranavSenthilnathan marked this conversation as resolved.
Show resolved Hide resolved
{
None,
Preserve,
IgnoreCycles
None = 0,
Preserve = 1,
IgnoreCycles = 2,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -846,5 +846,37 @@ public static void ContextWithInterpolatedAnnotations_WorksAsExpected()
internal partial class ContextWithInterpolatedAnnotations : JsonSerializerContext
{
}

[Fact]
public static async Task SupportsReferences()
{
ContextWithPreserveReference context = ContextWithPreserveReference.Default;
var selfRef = new SelfReference();
selfRef.Me = selfRef;

string expectedJson = """{"$id":"1","Me":{"$ref":"1"}}""";

string json = JsonSerializer.Serialize(selfRef, context.SelfReference);
Assert.Equal(expectedJson, json);

var stream = new Utf8MemoryStream();
await JsonSerializer.SerializeAsync(stream, selfRef, typeof(SelfReference), context);
Assert.Equal(expectedJson, stream.AsString());

SelfReference deserialized = JsonSerializer.Deserialize(expectedJson, context.SelfReference);
Assert.Same(deserialized, deserialized.Me);
}

[JsonSourceGenerationOptions(
ReferenceHandler = JsonKnownReferenceHandler.Preserve)]
[JsonSerializable(typeof(SelfReference))]
internal partial class ContextWithPreserveReference : JsonSerializerContext
{
}

internal class SelfReference
{
public SelfReference Me { get; set; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions()
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper,
ReadCommentHandling = JsonCommentHandling.Skip,
ReferenceHandler = ReferenceHandler.Preserve,
RespectNullableAnnotations = true,
RespectRequiredConstructorParameters = true,
UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
Expand Down Expand Up @@ -110,6 +111,7 @@ public static void ContextWithAllOptionsSet_GeneratesExpectedOptions()
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonKnownNamingPolicy.KebabCaseUpper,
ReadCommentHandling = JsonCommentHandling.Skip,
ReferenceHandler = JsonKnownReferenceHandler.Preserve,
RespectNullableAnnotations = true,
RespectRequiredConstructorParameters = true,
UnknownTypeHandling = JsonUnknownTypeHandling.JsonNode,
Expand Down
Loading