dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.44k stars 10.01k forks source link

Customizing deserialization of JS interop in blazor (again) #56926

Open FLAMESpl opened 3 months ago

FLAMESpl commented 3 months ago

Background and Motivation

Customizing deserialization of objects graphs in blazor js interop is cumbersome. I get the idea why overriding serializer options globally is no-go, but we could have make use of custom options per invocation of js interop. My rationale is, sometimes I do not have option to modify sources of contracts in order to put json converter attributes there, eg. when contract comes from nuget library. Most common situtation for me is deserialization of enum values - in typescript it is common for enum values to be strings and STJ does not provide a way to put json converter attribute on some top-level object that will cascade to properties of nested properties.

Proposed API

New method in IJSRuntime, IJSInProcessRuntime and related extension methods:

ValueTask<TValue> InvokeAsync<TValue>(string identifier, JsonSerializerOptions serializerOptions, object?[]? args);

Usage Examples

var myOptions = new JsonSerializerOptions { ... };
var jsFunctionParameters = [ 1, true, "param" ];
var myObject = jsRuntime.InvokeAsync<MyContract>("myJsFunction", myOptions, jsFunctionParameters);

Alternative Designs

Alternative design would involve some work around STJ to make its attributes more extensible.

Risks

Someone could carelessly decorate IJSRuntime service in a way it would apply one's custom serialization options to every JS interop call therefore breaking calls from external libraries.

Current Workaround

Currenlty to work around this problem I must deserialize objects to some partial types like JsonElement or byte[].

javiercn commented 3 months ago

@FLAMESpl thanks for contacting us.

You always have the option to serialize the data into a byte array and deserialize it on the other end however you see fit. Why does doesn't work for you?

FLAMESpl commented 3 months ago

I also do not have control over javascript function that I want to call as it comes from different application. I would need to create my custom JS function that calls 3rd party JS function and serializes its output to bytes. It is somewhat boilerplate or I am missing how to translate arbitrary JS object to byte array on dotnet side.

(I have tried jsRuntime.Invoke<byte[]>("jsFunction") - it does not work)

I can translate arbitrary js object to JsonElement but doesn't it come with some computation overhead?

javiercn commented 3 months ago

@FLAMESpl You can convert any object to a byte array and get a byte array back on the JS side that then you can parse however you want. We will pass in the byte arrays via JS interop through an optimized channel. There might be a slight overhead in doing it this way vs us doing it, but I don't think it'll be relevant in the majority of cases.

As for what you suggested, we always recommend defining the contracts for JS interop in assemblies that you control as opposed to relying on objects coming from 3rd party libraries. In that sense, our position has always been that if you have serialization needs beyond what the framework offers, you can either define new types to match the contract you expect or take care of the serialization/deserialization yourself and provide a byte array to us.

If we were to provide extensibility here, we would very likely offer a hook into the serialization process itself so that you could take charge of it rather than expose the JsonSerialization settings directly.