-
Notifications
You must be signed in to change notification settings - Fork 10.2k
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
Use System.Text.Json's source generated code to deserialize WebEvents #32374
Conversation
adde095
to
59c0035
Compare
|
||
// JsonSerializerOptions are tightly bound to the JsonContext. Cache it on first use using a copy | ||
// of the serializer settings. | ||
_jsonContext ??= new(new JsonSerializerOptions(jsonSerializerOptions)); |
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 the double wrapped JsonSerializerOptions?
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.
It looks like if you call new JsonContext(jsonSerializerOptions)
it's tied to that instance of JsonContext. We use these serializer options extensively for other serialization and I wanted to leave the default one "untouched".
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.
Might be good to add that as a comment.
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.
cc @layomia - any concerns here that the JsonSerializerOptions
object gets mutated when passed to a new JsonContext
?
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.
We have discussed allowing a one to many relationship between one options instance and multiple context instances, which would make using the jsonSerializerOptions
itself possible. I'll bring it up in the next review so we can close on it.
However, it would still be optimal to make an options instance immutable (wrt serialization configuration, e.g. PropertyNameCaseInsensitive
) once it has been bound with one or more context instances so that we can initialize the metadata reliably (knowing that the options won't change).
14230c5
to
1bb5fe1
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.
This looks good to me.
Also address missing "type" property on TouchEvent Fixes #32357
1bb5fe1
to
0ce96b4
Compare
@@ -245,4 +258,22 @@ private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson) | |||
return changeArgs; | |||
} | |||
} | |||
|
|||
[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.Serialization)] |
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.
@layomia - using JsonSourceGenerationMode.Serialization
means we only generate the "fast path" and not the "metadata".
What does this mean when we only support serialization right now, and not deserialization? Does it mean for deserialization it falls back to Reflection.Emit based deserialization?
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.
To use generated code for deserialization and avoiding Ref.Emit today, the setting would need to be JsonSourceGenerationMode.MetadataAndSerialization
. When deserializing, the serializer would fallback to generated code that references setters directly.
To make configuration here cleaner, we could change the enum to a flags enum:
[Flags]
public enum JsonSourceGenerationMode
{
Unspecified = 0,
Metadata = 1,
Serialization = 2,
Deserialization = 4, // Future
}
When we have fast-path logic for both read and write, the preferred setting (as needed) for simple scenarios would be Serialization | Deserialization
.
using System.Text.Json.Serialization; | ||
using Microsoft.AspNetCore.Components.RenderTree; | ||
|
||
namespace Microsoft.AspNetCore.Components.Web |
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.
Do we need this file?
...Assembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.WarningSuppressions.xml
Show resolved
Hide resolved
8e9ef23
to
7f42121
Compare
// of the serializer settings. | ||
if (_jsonContext is null) | ||
{ | ||
_jsonContext = new(new JsonSerializerOptions(jsonSerializerOptions)); |
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.
Can we move the ReadJsonSerializerOptions
call inside this if
block so it's only done once? Super minor detail I know, but helps to clarify that we only respect its value the first time, and it can't change after.
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.
Nice!
@pranavkm Is there already an issue filed about handling this for custom event args? |
@@ -245,4 +258,22 @@ private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson) | |||
return changeArgs; | |||
} | |||
} | |||
|
|||
[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] | |||
[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] |
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.
nit: this is a duplicate of the LOC above. Probably an error case the generator should catch since the mode could be different and the intention would be unclear. We'd throw or use last-one-wins..
[JsonSerializable(typeof(ProgressEventArgs), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] | ||
[JsonSerializable(typeof(TouchEventArgs), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] | ||
[JsonSerializable(typeof(WheelEventArgs), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] | ||
internal sealed partial class WebEventJsonContext : JsonSerializerContext |
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.
It looks like it would be cleaner to have a global JsonSourceGenerationMode
option per context:
public class JsonSourceGenerationModeAttribute : JsonAttribute
{
public JsonSourceGenerationModeAttribute(JsonSourceGenerationMode mode) { }
}
Building on #32374 (comment), we'd have
[JsonSourceGenerationMode(JsonSourceGenerationMode.Metadata | JsonSourceGenerationMode.Serialization)]
[JsonSerializable(typeof(WebEventDescriptor)]
[JsonSerializable(typeof(EventArgs)]
internal sealed partial class WebEventJsonContext : JsonSerializerContext
{
}
[JsonSerializable]
instances with an Unspecified
mode would fallback to the context-global mode.
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.
That does seem like a better alternative. It feels unusual to want fine-grained control over the serialization behavior on a per-type basis. That said, I imagine most users wouldn't really configure this since the extra IL for serialization isn't really noticeable.
@@ -245,4 +258,22 @@ private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson) | |||
return changeArgs; | |||
} | |||
} | |||
|
|||
[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] |
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.
Is there a reason you aren't just letting it default the "GenerationMode"?
[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)] | |
[JsonSerializable(typeof(WebEventDescriptor)] |
Also address missing "type" property on TouchEvent
Fixes #32357