diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs index bc88eec7..0cba15a4 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs +++ b/samples/BlazorServer.ExampleConsumer/Pages/ListenToMe.razor.cs @@ -25,7 +25,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) return; } - _transcript = await SessionStorage.GetItemAsync(TranscriptKey); + _transcript = await SessionStorage.GetItemAsync(TranscriptKey); } async Task OnRecognizeSpeechClick() diff --git a/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs index 67ded38c..c2ac8ff4 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs +++ b/samples/BlazorServer.ExampleConsumer/Pages/ReadToMe.razor.cs @@ -12,7 +12,7 @@ public sealed partial class ReadToMe : IAsyncDisposable string? _text = "Blazorators is an open-source project that strives to simplify JavaScript interop in Blazor. JavaScript interoperability is possible by parsing TypeScript type declarations and using this metadata to output corresponding C# types."; SpeechSynthesisVoice[] _voices = []; readonly IList _voiceSpeeds = - Enumerable.Range(0, 12).Select(i => (i + 1) * .25).ToList(); + [.. Enumerable.Range(0, 12).Select(i => (i + 1) * .25)]; double _voiceSpeed = 1.5; string? _selectedVoice; string? _elapsedTimeMessage = null; @@ -48,18 +48,18 @@ protected override async Task OnAfterRenderAsync(bool firstRender) await GetVoicesAsync(); - if (await LocalStorage.GetItemAsync(PreferredVoiceKey) + if (await LocalStorage.GetItemAsync(PreferredVoiceKey) is { Length: > 0 } voice) { _selectedVoice = voice; } - if (await LocalStorage.GetItemAsync(PreferredSpeedKey) + if (await LocalStorage.GetItemAsync(PreferredSpeedKey) is { Length: > 0 } s && double.TryParse(s, out var speed) && speed > 0) { _voiceSpeed = speed; } - if (await SessionStorage.GetItemAsync(TextKey) + if (await SessionStorage.GetItemAsync(TextKey) is { Length: > 0 } text) { _text = text; diff --git a/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs b/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs index 2be3f4a1..c1e0fec8 100644 --- a/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs +++ b/samples/BlazorServer.ExampleConsumer/Pages/TodoList.razor.cs @@ -32,28 +32,13 @@ async Task UpdateTodoItemsAsync() { if (key.StartsWith(TodoItem.IdPrefix)) { - var rawValue = await LocalStorage.GetItemAsync(key); - if (rawValue.FromJson() is TodoItem todo) + var todo = await LocalStorage.GetItemAsync(key); + if (todo is not null) { todos.Add(todo); _localStorageItems[key] = todo.ToString(); continue; } - if (rawValue is not null) - { - _localStorageItems[key] = rawValue; - continue; - } - if (bool.TryParse(rawValue, out var @bool)) - { - _localStorageItems[key] = @bool.ToString(); - continue; - } - if (decimal.TryParse(rawValue, out var num)) - { - _localStorageItems[key] = num.ToString(); - continue; - } } } diff --git a/src/Blazor.LocalStorage/Blazor.LocalStorage.csproj b/src/Blazor.LocalStorage/Blazor.LocalStorage.csproj index 0a78434e..380142a6 100644 --- a/src/Blazor.LocalStorage/Blazor.LocalStorage.csproj +++ b/src/Blazor.LocalStorage/Blazor.LocalStorage.csproj @@ -62,6 +62,7 @@ + diff --git a/src/Blazor.LocalStorage/ILocalStorageService.cs b/src/Blazor.LocalStorage/ILocalStorageService.cs index 9a17ccfe..fd4f4ff5 100644 --- a/src/Blazor.LocalStorage/ILocalStorageService.cs +++ b/src/Blazor.LocalStorage/ILocalStorageService.cs @@ -3,14 +3,15 @@ namespace Microsoft.JSInterop; -[JSAutoInterop( +[JSAutoGenericInterop( TypeName = "Storage", Implementation = "window.localStorage", HostingModel = BlazorHostingModel.Server, OnlyGeneratePureJS = true, Url = "https://developer.mozilla.org/docs/Web/API/Window/localStorage", - TypeDeclarationSources = + GenericMethodDescriptors = [ - "https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.dom.d.ts" + "getItem", + "setItem:value" ])] public partial interface ILocalStorageService; \ No newline at end of file diff --git a/src/Blazor.Serialization/Extensions/SerializationExtensions.cs b/src/Blazor.Serialization/Extensions/SerializationExtensions.cs index ed8a4851..2dc8da3e 100644 --- a/src/Blazor.Serialization/Extensions/SerializationExtensions.cs +++ b/src/Blazor.Serialization/Extensions/SerializationExtensions.cs @@ -19,17 +19,99 @@ public static class SerializationExtensions } }; - /// + /// public static TResult? FromJson( this string? json, JsonSerializerOptions? options = null) => json is { Length: > 0 } - ? Deserialize(json, options ?? _defaultOptions) + ? JsonSerializer.Deserialize(json, options ?? _defaultOptions) : default; - /// + /// + public static TResult? FromJson( + this string? json, + JsonTypeInfo? jsonTypeInfo) + { + return json switch + { + { Length: > 0 } => jsonTypeInfo switch + { + null => JsonSerializer.Deserialize(json, _defaultOptions), + _ => JsonSerializer.Deserialize(json, jsonTypeInfo) + }, + _ => default + }; + } + + /// + public static async ValueTask FromJsonAsync( + this ValueTask jsonTask, + JsonSerializerOptions? options = null) + { + var json = await jsonTask.ConfigureAwait(false); + + return json is { Length: > 0 } + ? JsonSerializer.Deserialize(json, options ?? _defaultOptions) + : default; + } + + /// + public static async ValueTask FromJsonAsync( + this ValueTask jsonTask, + JsonTypeInfo? jsonTypeInfo) + { + var json = await jsonTask.ConfigureAwait(false); + + return json switch + { + { Length: > 0 } => jsonTypeInfo switch + { + null => JsonSerializer.Deserialize(json, _defaultOptions), + _ => JsonSerializer.Deserialize(json, jsonTypeInfo) + }, + _ => default + }; + } + + /// public static string ToJson( this T value, JsonSerializerOptions? options = null) => - Serialize(value, options ?? _defaultOptions); + JsonSerializer.Serialize(value, options ?? _defaultOptions); + + /// + public static string ToJson( + this T value, + JsonTypeInfo? jsonTypeInfo) + { + return jsonTypeInfo switch + { + null => JsonSerializer.Serialize(value, _defaultOptions), + _ => JsonSerializer.Serialize(value, jsonTypeInfo) + }; + } + + /// + public static async ValueTask ToJsonAsync( + this ValueTask valueTask, + JsonSerializerOptions? options = null) + { + var value = await valueTask.ConfigureAwait(false); + + return JsonSerializer.Serialize(value, options ?? _defaultOptions); + } + + /// + public static async ValueTask ToJsonAsync( + this ValueTask valueTask, + JsonTypeInfo? jsonTypeInfo) + { + var value = await valueTask.ConfigureAwait(false); + + return jsonTypeInfo switch + { + null => JsonSerializer.Serialize(value, _defaultOptions), + _ => JsonSerializer.Serialize(value, jsonTypeInfo) + }; + } } diff --git a/src/Blazor.Serialization/GlobalUsings.cs b/src/Blazor.Serialization/GlobalUsings.cs index 010c5de7..2ffa81c4 100644 --- a/src/Blazor.Serialization/GlobalUsings.cs +++ b/src/Blazor.Serialization/GlobalUsings.cs @@ -3,4 +3,4 @@ global using System.Text.Json; global using System.Text.Json.Serialization; -global using static System.Text.Json.JsonSerializer; \ No newline at end of file +global using System.Text.Json.Serialization.Metadata; \ No newline at end of file diff --git a/src/Blazor.SessionStorage/Blazor.SessionStorage.csproj b/src/Blazor.SessionStorage/Blazor.SessionStorage.csproj index c88ab40d..c944eb6e 100644 --- a/src/Blazor.SessionStorage/Blazor.SessionStorage.csproj +++ b/src/Blazor.SessionStorage/Blazor.SessionStorage.csproj @@ -62,6 +62,7 @@ + diff --git a/src/Blazor.SessionStorage/ISessionStorageService.cs b/src/Blazor.SessionStorage/ISessionStorageService.cs index ffeaf52a..a633859f 100644 --- a/src/Blazor.SessionStorage/ISessionStorageService.cs +++ b/src/Blazor.SessionStorage/ISessionStorageService.cs @@ -4,10 +4,15 @@ namespace Microsoft.JSInterop; /// -[JSAutoInterop( +[JSAutoGenericInterop( TypeName = "Storage", Implementation = "window.sessionStorage", HostingModel = BlazorHostingModel.Server, OnlyGeneratePureJS = true, - Url = "https://developer.mozilla.org/docs/Web/API/Window/sessionStorage")] + Url = "https://developer.mozilla.org/docs/Web/API/Window/sessionStorage", + GenericMethodDescriptors = +[ + "getItem", + "setItem:value" + ])] public partial interface ISessionStorageService; \ No newline at end of file diff --git a/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs b/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs index 7b8cce27..d574c443 100644 --- a/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs +++ b/src/Blazor.SourceGenerators/Builders/SourceBuilder.cs @@ -75,8 +75,9 @@ internal SourceBuilder AppendUsingDeclarations() { if (_options is { SupportsGenerics: true }) { - _builder.Append($"using Blazor.Serialization.Extensions;{NewLine}"); - _builder.Append($"using System.Text.Json;{NewLine}"); + _builder.Append(value: $"using Blazor.Serialization.Extensions;{NewLine}"); + _builder.Append(value: $"using System.Text.Json;{NewLine}"); + _builder.Append(value: $"using System.Text.Json.Serialization.Metadata;{NewLine}"); } if (!_options.IsWebAssembly) diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs b/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs index fb167e4d..1e4113cc 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpTopLevelObject.cs @@ -1,8 +1,6 @@ // Copyright (c) David Pine. All rights reserved. // Licensed under the MIT License. -using Blazor.SourceGenerators.Builders; - namespace Blazor.SourceGenerators.CSharp; internal sealed partial record CSharpTopLevelObject(string RawTypeName) @@ -81,7 +79,7 @@ internal string ToInterfaceString( if (details.IsSerializable) { builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},") - .AppendRaw($"JsonSerializerOptions? options = null);") + .AppendRaw($"JsonTypeInfo<{MethodBuilderDetails.GenericTypeValue}>? typeInfo = null);") .AppendLine(); } else @@ -264,7 +262,7 @@ internal string ToImplementationString( if (details.IsSerializable) { builder.AppendRaw($"{parameter.ToParameterString(isGenericType)},"); - builder.AppendRaw($"JsonSerializerOptions? options){genericTypeParameterConstraint} =>"); + builder.AppendRaw($"JsonTypeInfo<{MethodBuilderDetails.GenericTypeValue}>? jsonTypeInfo){genericTypeParameterConstraint} =>"); } else { @@ -288,6 +286,7 @@ internal string ToImplementationString( builder.IncreaseIndentation() .AppendRaw($"\"{details.FullyQualifiedJavaScriptIdentifier}\","); + // Write method body / expression, and arguments to javaScript.Invoke* foreach (var (ai, parameter) in method.ParameterDefinitions.Select()) { @@ -298,7 +297,7 @@ internal string ToImplementationString( { // Overridden to control explicitly builder.AppendRaw($"{parameter.ToArgumentString(toJson: false)})"); - builder.AppendRaw($".FromJson{details.GenericTypeArgs}(options);"); + builder.AppendRaw($".FromJson{details.Suffix}{details.GenericTypeArgs}(jsonTypeInfo);"); } else { diff --git a/src/Blazor.SourceGenerators/CSharp/CSharpType.cs b/src/Blazor.SourceGenerators/CSharp/CSharpType.cs index c922f133..c4a856f1 100644 --- a/src/Blazor.SourceGenerators/CSharp/CSharpType.cs +++ b/src/Blazor.SourceGenerators/CSharp/CSharpType.cs @@ -99,7 +99,7 @@ public string ToArgumentString(bool toJson = false, bool asDelegate = false) : RawName.LowerCaseFirstLetter(); return toJson - ? $"{parameterName}.ToJson(options)" + ? $"{parameterName}.ToJson(jsonTypeInfo)" : parameterName; } } diff --git a/src/Blazor.SourceGenerators/Extensions/AttributeSyntaxExtensions.cs b/src/Blazor.SourceGenerators/Extensions/AttributeSyntaxExtensions.cs index be04fe0f..6110fd9b 100644 --- a/src/Blazor.SourceGenerators/Extensions/AttributeSyntaxExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/AttributeSyntaxExtensions.cs @@ -78,15 +78,14 @@ internal static GeneratorOptions GetGeneratorOptions( var trimmed = values.Trim(); var descriptors = trimmed.Split(','); - return descriptors + return [.. descriptors .Select(descriptor => { descriptor = descriptor .Replace("\"", "") .Trim(); return descriptor; - }) - .ToArray(); + })]; } return default; diff --git a/src/Blazor.SourceGenerators/Extensions/ListExtensions.cs b/src/Blazor.SourceGenerators/Extensions/ListExtensions.cs index d162363c..88c4d14b 100644 --- a/src/Blazor.SourceGenerators/Extensions/ListExtensions.cs +++ b/src/Blazor.SourceGenerators/Extensions/ListExtensions.cs @@ -5,7 +5,7 @@ namespace Blazor.SourceGenerators.Extensions; static class ListExtensions { - internal static IEnumerable<(Interation Index, T Item)> Select(this List list) + internal static IEnumerable<(Iteration Index, T Item)> Select(this List list) { var count = list.Count; for (var i = 0; i < count; ++i) @@ -15,7 +15,7 @@ static class ListExtensions } } -readonly record struct Interation( +readonly record struct Iteration( int Index, int Count) { diff --git a/src/Blazor.SourceGenerators/GlobalUsings.cs b/src/Blazor.SourceGenerators/GlobalUsings.cs index 3d2461b0..51d4bdea 100644 --- a/src/Blazor.SourceGenerators/GlobalUsings.cs +++ b/src/Blazor.SourceGenerators/GlobalUsings.cs @@ -7,6 +7,7 @@ global using System.Text; global using System.Text.RegularExpressions; global using Blazor.SourceGenerators.CSharp; +global using Blazor.SourceGenerators.Builders; global using Blazor.SourceGenerators.Diagnostics; global using Blazor.SourceGenerators.Expressions; global using Blazor.SourceGenerators.Extensions;