-
Notifications
You must be signed in to change notification settings - Fork 5k
Add JSON source-gen mode that emits serialization logic #53212
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
Conversation
Tagging subscribers to this area: @eiriktsarpalis, @layomia Issue DetailsContributes to #51945. Users kick off source generation by providing a partial, derived context class; and indicate serializable types and run-time options (optional): namespace System.Text.Json.SourceGeneration.Tests
{
[JsonSerializerOptions(
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault,
IgnoreReadOnlyProperties = true,
IgnoreRuntimeCustomConverters = true,
NamingPolicy = JsonKnownNamingPolicy.BuiltInCamelCase)]
[JsonSerializable(typeof(JsonMessage), GenerationMode = JsonSourceGenerationMode.Serialization)]
internal partial class JsonContext : JsonSerializerContext
{
}
public class JsonMessage
{
public string Message { get; set; }
public int Length => Message?.Length ?? 0; // Read-only property
}
} Generated code (click to view)// <auto-generated/>
namespace System.Text.Json.SourceGeneration.Tests
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Text.Json.SourceGeneration", "6.0.0.0")]
internal partial class JsonContext : global::System.Text.Json.Serialization.JsonSerializerContext
{
private static global::System.Text.Json.JsonSerializerOptions s_defaultOptions { get; } = new global::System.Text.Json.JsonSerializerOptions()
{
DefaultIgnoreCondition = global::System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault,
IgnoreReadOnlyFields = false,
IgnoreReadOnlyProperties = true,
IncludeFields = false,
WriteIndented = false,
PropertyNamingPolicy = global::System.Text.Json.JsonNamingPolicy.CamelCase
};
private static global::System.Text.Json.SourceGeneration.Tests.JsonContext s_defaultContext;
public static global::System.Text.Json.SourceGeneration.Tests.JsonContext Default => s_defaultContext ??= new global::System.Text.Json.SourceGeneration.Tests.JsonContext(new global::System.Text.Json.JsonSerializerOptions(s_defaultOptions));
private static global::System.Text.Json.JsonEncodedText messagePropName = global::System.Text.Json.JsonEncodedText.Encode("message");
private static global::System.Text.Json.JsonEncodedText lengthPropName = global::System.Text.Json.JsonEncodedText.Encode("length");
public JsonContext() : base(null, s_defaultOptions)
{
}
public JsonContext(global::System.Text.Json.JsonSerializerOptions options) : base(options, s_defaultOptions)
{
}
public override global::System.Text.Json.Serialization.Metadata.JsonTypeInfo GetTypeInfo(global::System.Type type)
{
if (type == typeof(global::System.Text.Json.SourceGeneration.Tests.JsonMessage))
{
return this.JsonMessage;
}
return null!;
}
private global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.JsonMessage> _JsonMessage;
public global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.JsonMessage> JsonMessage
{
get
{
if (_JsonMessage == null)
{
global::System.Text.Json.Serialization.Metadata.JsonTypeInfo<global::System.Text.Json.SourceGeneration.Tests.JsonMessage> objectInfo = global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.CreateObjectInfo<global::System.Text.Json.SourceGeneration.Tests.JsonMessage>();
_JsonMessage = objectInfo;
global::System.Text.Json.Serialization.Metadata.JsonMetadataServices.InitializeObjectInfo(
objectInfo,
Options,
createObjectFunc: static () => new global::System.Text.Json.SourceGeneration.Tests.JsonMessage(),
propInitFunc: null,
default,
serializeFunc: JsonMessageSerialize);
}
return _JsonMessage;
}
}
private static void JsonMessageSerialize(global::System.Text.Json.Utf8JsonWriter writer, global::System.Text.Json.SourceGeneration.Tests.JsonMessage value)
{
if (value == null)
{
writer.WriteNullValue();
return;
}
writer.WriteStartObject();
if (value.Message != null)
{
writer.WriteString(messagePropName, value.Message);
}
writer.WriteEndObject();
}
}
} To use generated serialization code: Interacting with generated func directly: using MemoryStream ms = new();
Utf8JsonWriter writer = new(ms);
JsonContext.Default.JsonMessage.Serialize!(writer, new JsonMessage { Message = "Hello, world!" });
writer.Flush(); Interacting via JsonSerializer.Serialize(new JsonMessage { Message = "Hello, world!" }, JsonContext.Default.JsonMessage); FYI @pranavkm @SteveSandersonMS @terrajobst @ericstj @jkotas @stephentoub @davidfowl @SamMonoRT @CoffeeFlux
|
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
...ries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonStringEnumConverter.cs
Outdated
Show resolved
Hide resolved
...libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfo.Cache.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextTests.cs
Outdated
Show resolved
Hide resolved
@@ -165,15 +171,19 @@ public void TypeDiscoveryWithRenamedAttribute() | |||
using System.Text.Json.Serialization; | |||
using ReferencedAssembly; | |||
|
|||
using @JsonSerializable = System.Runtime.Serialization.ContractNamespaceAttribute; | |||
using @JsonSerializable = System.Runtime.Serialization.CollectionDataContractAttribute ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use S.R.S.CollectionDataContractAttribute here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed to this because, like JsonSerializableAttribute
, it is applied to class
es.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is modeling type aliasing. In this case, we have two attributes switching names. The test makes sure that our check for [JsonSerializable(Type)]
guards against such tricks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. A comment here about that would be useful. Thanks
.../System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/SerializationContextTests.cs
Show resolved
Hide resolved
...ries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoInternalOfT.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs
Outdated
Show resolved
Hide resolved
...ries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/MixedModeContextTests.cs
Show resolved
Hide resolved
.../System.Text.Json/src/System/Text/Json/Serialization/Attributes/JsonSerializableAttribute.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http.Json/tests/FunctionalTests/JsonContext/JsonContext.cs
Show resolved
Hide resolved
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs
Outdated
Show resolved
Hide resolved
...t.Json/tests/System.Text.Json.SourceGeneration.Tests/MetadataAndSerializationContextTests.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonTypeInfoOfT.cs
Outdated
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs
Outdated
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs
Outdated
Show resolved
Hide resolved
...ystem.Text.Json/src/System/Text/Json/Serialization/Metadata/JsonMetadataServicesConverter.cs
Outdated
Show resolved
Hide resolved
330722b
to
f855af8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm. Let’s get this in so we can get a preview s out. Any additional feedback can be addressed in follow up prs.
All these PGO failures were caused by #53301, @AndyAyersMS and @BruceForstall have helped get this fixed, but we can't get rid of the badges without pushing new changes / resetting CI. Since we know this PR didn't introduce the PGO failures we can safely ignore those. |
/backport to release/6.0-preview5 |
Started backporting to release/6.0-preview5: https://github.com/dotnet/runtime/actions/runs/881152471 |
These breaking changes have already hit customers & the APIs have changed further since then - dotnet/docs#26200. |
Contributes to #51945.
Contributes to #52279.
Users kick off source generation by providing a partial, derived context class; and indicate serializable types and run-time options (optional):
Generated code (click to view)
To use generated serialization code:
Interacting with generated func directly:
Interacting via
JsonSerializer
:or
FYI @pranavkm @SteveSandersonMS @terrajobst @ericstj @jkotas @stephentoub @davidfowl @SamMonoRT @CoffeeFlux