Start progress on AoT. (#39051)

* starting AoT flag push

* Few more

* bookmarks

* Really? The VM project compiles?

* Disable publish AOT before we really testing it.

---------

Co-authored-by: Mike Griese <migrie@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
This commit is contained in:
Clint Rutkas
2025-04-27 05:44:47 +00:00
committed by GitHub
parent 30df5e0df2
commit ba230eca07
17 changed files with 144 additions and 75 deletions

View File

@@ -49,7 +49,7 @@ public partial class AppStateModel : ObservableObject
// Read the JSON content from the file // Read the JSON content from the file
var jsonContent = File.ReadAllText(FilePath); var jsonContent = File.ReadAllText(FilePath);
var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, _deserializerOptions); var loaded = JsonSerializer.Deserialize<AppStateModel>(jsonContent, JsonSerializationContext.Default.AppStateModel);
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse"); Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
@@ -73,7 +73,7 @@ public partial class AppStateModel : ObservableObject
try try
{ {
// Serialize the main dictionary to JSON and save it to the file // Serialize the main dictionary to JSON and save it to the file
var settingsJson = JsonSerializer.Serialize(model, _serializerOptions); var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel);
// Is it valid JSON? // Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings) if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
@@ -89,7 +89,7 @@ public partial class AppStateModel : ObservableObject
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null; savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
} }
var serialized = savedSettings.ToJsonString(_serializerOptions); var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
File.WriteAllText(FilePath, serialized); File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should // TODO: Instead of just raising the event here, we should
@@ -122,18 +122,19 @@ public partial class AppStateModel : ObservableObject
return Path.Combine(directory, "state.json"); return Path.Combine(directory, "state.json");
} }
private static readonly JsonSerializerOptions _serializerOptions = new() // [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
{ // private static readonly JsonSerializerOptions _serializerOptions = new()
WriteIndented = true, // {
Converters = { new JsonStringEnumConverter() }, // WriteIndented = true,
}; // Converters = { new JsonStringEnumConverter() },
// };
private static readonly JsonSerializerOptions _deserializerOptions = new() // private static readonly JsonSerializerOptions _deserializerOptions = new()
{ // {
PropertyNameCaseInsensitive = true, // PropertyNameCaseInsensitive = true,
IncludeFields = true, // IncludeFields = true,
AllowTrailingCommas = true, // AllowTrailingCommas = true,
PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate, // PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
ReadCommentHandling = JsonCommentHandling.Skip, // ReadCommentHandling = JsonCommentHandling.Skip,
}; // };
} }

View File

@@ -13,12 +13,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
{ {
public CreatedExtensionForm(string name, string displayName, string path) public CreatedExtensionForm(string name, string displayName, string path)
{ {
var serializeString = (string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
TemplateJson = CardTemplate; TemplateJson = CardTemplate;
DataJson = $$""" DataJson = $$"""
{ {
"name": {{JsonSerializer.Serialize(name)}}, "name": {{serializeString(name)}},
"directory": {{JsonSerializer.Serialize(path)}}, "directory": {{serializeString(path)}},
"displayName": {{JsonSerializer.Serialize(displayName)}} "displayName": {{serializeString(displayName)}}
} }
"""; """;
_name = name; _name = name;
@@ -28,13 +29,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
public override ICommandResult SubmitForm(string inputs, string data) public override ICommandResult SubmitForm(string inputs, string data)
{ {
JsonObject? dataInput = JsonNode.Parse(data)?.AsObject(); var dataInput = JsonNode.Parse(data)?.AsObject();
if (dataInput == null) if (dataInput == null)
{ {
return CommandResult.KeepOpen(); return CommandResult.KeepOpen();
} }
string verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty; var verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty;
return verb switch return verb switch
{ {
"sln" => OpenSolution(), "sln" => OpenSolution(),
@@ -47,7 +48,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
private ICommandResult OpenSolution() private ICommandResult OpenSolution()
{ {
string[] parts = [_path, _name, $"{_name}.sln"]; string[] parts = [_path, _name, $"{_name}.sln"];
string pathToSolution = Path.Combine(parts); var pathToSolution = Path.Combine(parts);
ShellHelpers.OpenInShell(pathToSolution); ShellHelpers.OpenInShell(pathToSolution);
return CommandResult.Hide(); return CommandResult.Hide();
} }
@@ -55,7 +56,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase
private ICommandResult OpenDirectory() private ICommandResult OpenDirectory()
{ {
string[] parts = [_path, _name]; string[] parts = [_path, _name];
string pathToDir = Path.Combine(parts); var pathToDir = Path.Combine(parts);
ShellHelpers.OpenInShell(pathToDir); ShellHelpers.OpenInShell(pathToDir);
return CommandResult.Hide(); return CommandResult.Hide();
} }

View File

@@ -194,9 +194,8 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase
Directory.Delete(tempDir, true); Directory.Delete(tempDir, true);
} }
private string FormatJsonString(string str) private string FormatJsonString(string str) =>
{
// Escape the string for JSON // Escape the string for JSON
return JsonSerializer.Serialize(str); JsonSerializer.Serialize(str, JsonSerializationContext.Default.String);
}
} }

View File

@@ -51,15 +51,16 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
// If we fail to parse the card JSON, then display _our own card_ // If we fail to parse the card JSON, then display _our own card_
// with the exception // with the exception
AdaptiveCardTemplate template = new(ErrorCardJson); AdaptiveCardTemplate template = new(ErrorCardJson);
var serializeString = (string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
// todo: we could probably stick Card.Errors in there too // todo: we could probably stick Card.Errors in there too
var dataJson = $$""" var dataJson = $$"""
{ {
"error_message": {{JsonSerializer.Serialize(e.Message)}}, "error_message": {{serializeString(e.Message)}},
"error_stack": {{JsonSerializer.Serialize(e.StackTrace)}}, "error_stack": {{serializeString(e.StackTrace)}},
"inner_exception": {{JsonSerializer.Serialize(e.InnerException?.Message)}}, "inner_exception": {{serializeString(e.InnerException?.Message)}},
"template_json": {{JsonSerializer.Serialize(TemplateJson)}}, "template_json": {{serializeString(TemplateJson)}},
"data_json": {{JsonSerializer.Serialize(DataJson)}} "data_json": {{serializeString(DataJson)}}
} }
"""; """;
var cardJson = template.Expand(dataJson); var cardJson = template.Expand(dataJson);

View File

@@ -1,5 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" /> <Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup> <PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -67,4 +69,15 @@
</Content> </Content>
</ItemGroup> </ItemGroup>
<!-- Just mark it as AOT compatible. Do not publish with AOT now. We need fully test before we really publish it as AOT enabled-->
<!--<PropertyGroup>
<SelfContained>true</SelfContained>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<PublishTrimmed>true</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
--><!-- <DisableRuntimeMarshalling>true</DisableRuntimeMarshalling> --><!--
<PublishAot>true</PublishAot>
<EnableMsixTooling>true</EnableMsixTooling>
</PropertyGroup>-->
</Project> </Project>

View File

@@ -11,7 +11,7 @@ using Windows.Foundation.Collections;
namespace Microsoft.CmdPal.UI.ViewModels.Models; namespace Microsoft.CmdPal.UI.ViewModels.Models;
public class ExtensionService : IExtensionService, IDisposable public partial class ExtensionService : IExtensionService, IDisposable
{ {
public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded; public event TypedEventHandler<IExtensionService, IEnumerable<IExtensionWrapper>>? OnExtensionAdded;

View File

@@ -12,6 +12,7 @@ using Windows.Win32;
using Windows.Win32.System.Com; using Windows.Win32.System.Com;
using WinRT; using WinRT;
// [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling]
namespace Microsoft.CmdPal.UI.ViewModels.Models; namespace Microsoft.CmdPal.UI.ViewModels.Models;
public class ExtensionWrapper : IExtensionWrapper public class ExtensionWrapper : IExtensionWrapper
@@ -113,25 +114,36 @@ public class ExtensionWrapper : IExtensionWrapper
// -2147467262: E_NOINTERFACE // -2147467262: E_NOINTERFACE
// -2147024893: E_PATH_NOT_FOUND // -2147024893: E_PATH_NOT_FOUND
var guid = typeof(IExtension).GUID; var guid = typeof(IExtension).GUID;
var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj);
if (hr.Value == -2147024893) unsafe
{ {
Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted."); var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj);
// We don't really need to throw this exception. if (hr.Value == -2147024893)
// We'll just return out nothing. {
return; Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted.");
// We don't really need to throw this exception.
// We'll just return out nothing.
return;
}
extensionPtr = Marshal.GetIUnknownForObject((nint)extensionObj);
if (hr < 0)
{
Logger.LogDebug($"Failed to instantiate {ExtensionDisplayName}: {hr}");
Marshal.ThrowExceptionForHR(hr);
}
// extensionPtr = Marshal.GetIUnknownForObject(extensionObj);
extensionPtr = (nint)extensionObj;
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
_extensionObject = MarshalInterface<IExtension>.FromAbi(extensionPtr);
} }
extensionPtr = Marshal.GetIUnknownForObject(extensionObj);
if (hr < 0)
{
Logger.LogDebug($"Failed to instantiate {ExtensionDisplayName}: {hr}");
Marshal.ThrowExceptionForHR(hr);
}
_extensionObject = MarshalInterface<IExtension>.FromAbi(extensionPtr);
} }
finally finally
{ {

View File

@@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": false
}

View File

@@ -99,7 +99,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext
//// Run on background thread from ListPage.xaml.cs //// Run on background thread from ListPage.xaml.cs
[RelayCommand] [RelayCommand]
private Task<bool> InitializeAsync() internal Task<bool> InitializeAsync()
{ {
// TODO: We may want a SemaphoreSlim lock here. // TODO: We may want a SemaphoreSlim lock here.

View File

@@ -10,7 +10,7 @@ namespace Microsoft.CmdPal.UI.ViewModels;
public partial class RecentCommandsManager : ObservableObject public partial class RecentCommandsManager : ObservableObject
{ {
[JsonInclude] [JsonInclude]
private List<HistoryItem> History { get; set; } = []; internal List<HistoryItem> History { get; set; } = [];
public RecentCommandsManager() public RecentCommandsManager()
{ {

View File

@@ -93,7 +93,7 @@ public partial class SettingsModel : ObservableObject
// Read the JSON content from the file // Read the JSON content from the file
var jsonContent = File.ReadAllText(FilePath); var jsonContent = File.ReadAllText(FilePath);
var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, _deserializerOptions); var loaded = JsonSerializer.Deserialize<SettingsModel>(jsonContent, JsonSerializationContext.Default.SettingsModel);
Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse"); Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse");
@@ -117,7 +117,7 @@ public partial class SettingsModel : ObservableObject
try try
{ {
// Serialize the main dictionary to JSON and save it to the file // Serialize the main dictionary to JSON and save it to the file
var settingsJson = JsonSerializer.Serialize(model, _serializerOptions); var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.SettingsModel);
// Is it valid JSON? // Is it valid JSON?
if (JsonNode.Parse(settingsJson) is JsonObject newSettings) if (JsonNode.Parse(settingsJson) is JsonObject newSettings)
@@ -133,7 +133,7 @@ public partial class SettingsModel : ObservableObject
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null; savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
} }
var serialized = savedSettings.ToJsonString(_serializerOptions); var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options);
File.WriteAllText(FilePath, serialized); File.WriteAllText(FilePath, serialized);
// TODO: Instead of just raising the event here, we should // TODO: Instead of just raising the event here, we should
@@ -166,19 +166,34 @@ public partial class SettingsModel : ObservableObject
return Path.Combine(directory, "settings.json"); return Path.Combine(directory, "settings.json");
} }
private static readonly JsonSerializerOptions _serializerOptions = new() // [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
{ // private static readonly JsonSerializerOptions _serializerOptions = new()
WriteIndented = true, // {
Converters = { new JsonStringEnumConverter() }, // WriteIndented = true,
}; // Converters = { new JsonStringEnumConverter() },
// };
// private static readonly JsonSerializerOptions _deserializerOptions = new()
// {
// PropertyNameCaseInsensitive = true,
// IncludeFields = true,
// Converters = { new JsonStringEnumConverter() },
// AllowTrailingCommas = true,
// };
}
private static readonly JsonSerializerOptions _deserializerOptions = new() [JsonSerializable(typeof(float))]
{ [JsonSerializable(typeof(int))]
PropertyNameCaseInsensitive = true, [JsonSerializable(typeof(string))]
IncludeFields = true, [JsonSerializable(typeof(bool))]
Converters = { new JsonStringEnumConverter() }, [JsonSerializable(typeof(HistoryItem))]
AllowTrailingCommas = true, [JsonSerializable(typeof(SettingsModel))]
}; [JsonSerializable(typeof(AppStateModel))]
[JsonSerializable(typeof(List<HistoryItem>), TypeInfoPropertyName = "HistoryList")]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
internal sealed partial class JsonSerializationContext : JsonSerializerContext
{
} }
public enum MonitorBehavior public enum MonitorBehavior

View File

@@ -109,9 +109,12 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
// TODO GH #239 switch back when using the new MD text block // TODO GH #239 switch back when using the new MD text block
// _ = _queue.EnqueueAsync(() => // _ = _queue.EnqueueAsync(() =>
_ = Task.Factory.StartNew( _ = Task.Factory.StartNew(
() => async () =>
{ {
var result = (bool)viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!; // bool f = await viewModel.InitializeCommand.ExecutionTask.;
// var result = viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!;
// var result = viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault<bool?>()!;
var result = await viewModel.InitializeAsync();
CurrentPage = viewModel; // result ? viewModel : null; CurrentPage = viewModel; // result ? viewModel : null;
////LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error; ////LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error;

View File

@@ -34,7 +34,7 @@ internal sealed partial class AddBookmarkForm : FormContent
"style": "text", "style": "text",
"id": "name", "id": "name",
"label": "{{Resources.bookmarks_form_name_label}}", "label": "{{Resources.bookmarks_form_name_label}}",
"value": {{JsonSerializer.Serialize(name)}}, "value": {{JsonSerializer.Serialize(name, BookmarkSerializationContext.Default.String)}},
"isRequired": true, "isRequired": true,
"errorMessage": "{{Resources.bookmarks_form_name_required}}" "errorMessage": "{{Resources.bookmarks_form_name_required}}"
}, },
@@ -42,7 +42,7 @@ internal sealed partial class AddBookmarkForm : FormContent
"type": "Input.Text", "type": "Input.Text",
"style": "text", "style": "text",
"id": "bookmark", "id": "bookmark",
"value": {{JsonSerializer.Serialize(url)}}, "value": {{JsonSerializer.Serialize(url, BookmarkSerializationContext.Default.String)}},
"label": "{{Resources.bookmarks_form_bookmark_label}}", "label": "{{Resources.bookmarks_form_bookmark_label}}",
"isRequired": true, "isRequired": true,
"errorMessage": "{{Resources.bookmarks_form_bookmark_required}}" "errorMessage": "{{Resources.bookmarks_form_bookmark_required}}"

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Microsoft.CmdPal.Ext.Bookmarks;
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(BookmarkData))]
[JsonSerializable(typeof(Bookmarks))]
[JsonSerializable(typeof(List<BookmarkData>), TypeInfoPropertyName = "BookmarkList")]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
internal sealed partial class BookmarkSerializationContext : JsonSerializerContext
{
}

View File

@@ -28,7 +28,7 @@ public sealed class Bookmarks
if (!string.IsNullOrEmpty(jsonStringReading)) if (!string.IsNullOrEmpty(jsonStringReading))
{ {
data = JsonSerializer.Deserialize<Bookmarks>(jsonStringReading, _jsonOptions) ?? new Bookmarks(); data = JsonSerializer.Deserialize<Bookmarks>(jsonStringReading, BookmarkSerializationContext.Default.Bookmarks) ?? new Bookmarks();
} }
} }
@@ -37,7 +37,7 @@ public sealed class Bookmarks
public static void WriteToFile(string path, Bookmarks data) public static void WriteToFile(string path, Bookmarks data)
{ {
var jsonString = JsonSerializer.Serialize(data, _jsonOptions); var jsonString = JsonSerializer.Serialize(data, BookmarkSerializationContext.Default.Bookmarks);
File.WriteAllText(BookmarksCommandProvider.StateJsonPath(), jsonString); File.WriteAllText(BookmarksCommandProvider.StateJsonPath(), jsonString);
} }

View File

@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" /> <Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<PropertyGroup> <PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.Bookmarks</RootNamespace> <RootNamespace>Microsoft.CmdPal.Ext.Bookmarks</RootNamespace>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -16,7 +17,7 @@
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" /> <ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" /> <ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Properties\Resources.Designer.cs"> <Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon> <DependentUpon>Resources.resx</DependentUpon>
@@ -24,7 +25,7 @@
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Update="Assets\Bookmark.png"> <Content Update="Assets\Bookmark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -39,5 +40,5 @@
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -16,7 +16,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit;
[JsonSerializable(typeof(List<ChoiceSetSetting>))] [JsonSerializable(typeof(List<ChoiceSetSetting>))]
[JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")] [JsonSerializable(typeof(Dictionary<string, object>), TypeInfoPropertyName = "Dictionary")]
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)] [JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
internal partial class JsonSerializationContext : JsonSerializerContext internal partial class JsonSerializationContext : JsonSerializerContext
{ {
} }