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

Use System.Text.Json's source generated code to deserialize WebEvents #32374

Merged
merged 6 commits into from
Jun 7, 2021

Conversation

pranavkm
Copy link
Contributor

@pranavkm pranavkm commented May 4, 2021

Also address missing "type" property on TouchEvent

Fixes #32357

@ghost ghost added the area-blazor Includes: Blazor, Razor Components label May 4, 2021
@pranavkm pranavkm force-pushed the prkrishn/use-source-generator branch 2 times, most recently from adde095 to 59c0035 Compare May 4, 2021 16:50
@pranavkm pranavkm marked this pull request as ready for review May 4, 2021 16:52
@pranavkm pranavkm requested a review from a team as a code owner May 4, 2021 16:52
@pranavkm pranavkm requested review from layomia and eerhardt May 4, 2021 16:53

// JsonSerializerOptions are tightly bound to the JsonContext. Cache it on first use using a copy
// of the serializer settings.
_jsonContext ??= new(new JsonSerializerOptions(jsonSerializerOptions));
Copy link
Member

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?

Copy link
Contributor Author

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".

Copy link
Member

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.

Copy link
Member

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?

Copy link

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).

@pranavkm pranavkm removed the request for review from a team May 4, 2021 18:02
@pranavkm pranavkm added the blocked The work on this issue is blocked due to some dependency label May 4, 2021
@pranavkm pranavkm force-pushed the prkrishn/use-source-generator branch 2 times, most recently from 14230c5 to 1bb5fe1 Compare May 15, 2021 22:22
@pranavkm pranavkm removed the blocked The work on this issue is blocked due to some dependency label May 15, 2021
Copy link
Member

@eerhardt eerhardt left a 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.

src/Components/Server/src/Circuits/CircuitHost.cs Outdated Show resolved Hide resolved
@pranavkm pranavkm force-pushed the prkrishn/use-source-generator branch from 1bb5fe1 to 0ce96b4 Compare June 4, 2021 21:22
@pranavkm pranavkm requested a review from Pilchie as a code owner June 4, 2021 21:22
@@ -245,4 +258,22 @@ private static ChangeEventArgs DeserializeChangeEventArgs(string eventArgsJson)
return changeArgs;
}
}

[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.Serialization)]
Copy link
Member

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?

Copy link

@layomia layomia Jun 7, 2021

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
Copy link
Member

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?

@pranavkm pranavkm force-pushed the prkrishn/use-source-generator branch from 8e9ef23 to 7f42121 Compare June 4, 2021 21:33
// of the serializer settings.
if (_jsonContext is null)
{
_jsonContext = new(new JsonSerializerOptions(jsonSerializerOptions));
Copy link
Member

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.

Copy link
Member

@SteveSandersonMS SteveSandersonMS left a comment

Choose a reason for hiding this comment

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

Nice!

@SteveSandersonMS
Copy link
Member

@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)]
Copy link

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
Copy link

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.

Copy link
Contributor Author

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)]
Copy link
Member

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"?

Suggested change
[JsonSerializable(typeof(WebEventDescriptor), GenerationMode = JsonSourceGenerationMode.MetadataAndSerialization)]
[JsonSerializable(typeof(WebEventDescriptor)]

@pranavkm pranavkm enabled auto-merge (squash) June 7, 2021 16:31
@pranavkm pranavkm merged commit fcd4ed7 into main Jun 7, 2021
@pranavkm pranavkm deleted the prkrishn/use-source-generator branch June 7, 2021 19:19
@ghost ghost added this to the 6.0-preview6 milestone Jun 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
4 participants