From 4ce4299ca2a6b11332f2341c69f40efd7205282f Mon Sep 17 00:00:00 2001 From: Andrey Sukharev Date: Wed, 22 Mar 2023 01:41:19 +0300 Subject: Use source generated json serializers in order to improve code trimming (#4094) * Use source generated json serializers in order to improve code trimming * Use strongly typed github releases model to fetch updates instead of raw Newtonsoft.Json parsing * Use separate model for LogEventArgs serialization * Make dynamic object formatter static. Fix string builder pooling. * Do not inherit json version of LogEventArgs from EventArgs * Fix extra space in object formatting * Write log json directly to stream instead of using buffer writer * Rebase fixes * Rebase fixes * Rebase fixes * Enforce block-scoped namespaces in the solution. Convert style for existing code * Apply suggestions from code review Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Rebase indent fix * Fix indent * Delete unnecessary json properties * Rebase fix * Remove overridden json property names as they are handled in the options * Apply suggestions from code review Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> * Use default json options in github api calls * Indentation and spacing fixes --------- Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com> --- Ryujinx.Common/Utilities/CommonJsonContext.cs | 11 ++ Ryujinx.Common/Utilities/JsonHelper.cs | 122 ++++++++++----------- .../Utilities/TypedStringEnumConverter.cs | 34 ++++++ 3 files changed, 100 insertions(+), 67 deletions(-) create mode 100644 Ryujinx.Common/Utilities/CommonJsonContext.cs create mode 100644 Ryujinx.Common/Utilities/TypedStringEnumConverter.cs (limited to 'Ryujinx.Common/Utilities') diff --git a/Ryujinx.Common/Utilities/CommonJsonContext.cs b/Ryujinx.Common/Utilities/CommonJsonContext.cs new file mode 100644 index 00000000..d7b3f78c --- /dev/null +++ b/Ryujinx.Common/Utilities/CommonJsonContext.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Utilities +{ + [JsonSerializable(typeof(string[]), TypeInfoPropertyName = "StringArray")] + [JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "StringDictionary")] + public partial class CommonJsonContext : JsonSerializerContext + { + } +} \ No newline at end of file diff --git a/Ryujinx.Common/Utilities/JsonHelper.cs b/Ryujinx.Common/Utilities/JsonHelper.cs index 36f39114..9a2d6f18 100644 --- a/Ryujinx.Common/Utilities/JsonHelper.cs +++ b/Ryujinx.Common/Utilities/JsonHelper.cs @@ -1,15 +1,62 @@ -using Ryujinx.Common.Configuration.Hid; -using Ryujinx.Common.Configuration.Hid.Controller.Motion; -using System.IO; +using System.IO; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; namespace Ryujinx.Common.Utilities { public class JsonHelper { - public static JsonNamingPolicy SnakeCase { get; } + private static readonly JsonNamingPolicy SnakeCasePolicy = new SnakeCaseNamingPolicy(); + private const int DefaultFileWriteBufferSize = 4096; + + /// + /// Creates new serializer options with default settings. + /// + /// + /// It is REQUIRED for you to save returned options statically or as a part of static serializer context + /// in order to avoid performance issues. You can safely modify returned options for your case before storing. + /// + public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true) + { + JsonSerializerOptions options = new() + { + DictionaryKeyPolicy = SnakeCasePolicy, + PropertyNamingPolicy = SnakeCasePolicy, + WriteIndented = indented, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + + return options; + } + + public static string Serialize(T value, JsonTypeInfo typeInfo) + { + return JsonSerializer.Serialize(value, typeInfo); + } + + public static T Deserialize(string value, JsonTypeInfo typeInfo) + { + return JsonSerializer.Deserialize(value, typeInfo); + } + + public static void SerializeToFile(string filePath, T value, JsonTypeInfo typeInfo) + { + using FileStream file = File.Create(filePath, DefaultFileWriteBufferSize, FileOptions.WriteThrough); + JsonSerializer.Serialize(file, value, typeInfo); + } + + public static T DeserializeFromFile(string filePath, JsonTypeInfo typeInfo) + { + using FileStream file = File.OpenRead(filePath); + return JsonSerializer.Deserialize(file, typeInfo); + } + + public static void SerializeToStream(Stream stream, T value, JsonTypeInfo typeInfo) + { + JsonSerializer.Serialize(stream, value, typeInfo); + } private class SnakeCaseNamingPolicy : JsonNamingPolicy { @@ -20,7 +67,7 @@ namespace Ryujinx.Common.Utilities return name; } - StringBuilder builder = new StringBuilder(); + StringBuilder builder = new(); for (int i = 0; i < name.Length; i++) { @@ -34,7 +81,7 @@ namespace Ryujinx.Common.Utilities } else { - builder.Append("_"); + builder.Append('_'); builder.Append(char.ToLowerInvariant(c)); } } @@ -47,64 +94,5 @@ namespace Ryujinx.Common.Utilities return builder.ToString(); } } - - static JsonHelper() - { - SnakeCase = new SnakeCaseNamingPolicy(); - } - - public static JsonSerializerOptions GetDefaultSerializerOptions(bool prettyPrint = false) - { - JsonSerializerOptions options = new JsonSerializerOptions - { - DictionaryKeyPolicy = SnakeCase, - PropertyNamingPolicy = SnakeCase, - WriteIndented = prettyPrint, - AllowTrailingCommas = true, - ReadCommentHandling = JsonCommentHandling.Skip - }; - - options.Converters.Add(new JsonStringEnumConverter()); - options.Converters.Add(new JsonInputConfigConverter()); - options.Converters.Add(new JsonMotionConfigControllerConverter()); - - return options; - } - - public static T Deserialize(Stream stream) - { - using (BinaryReader reader = new BinaryReader(stream)) - { - return JsonSerializer.Deserialize(reader.ReadBytes((int)(stream.Length - stream.Position)), GetDefaultSerializerOptions()); - } - } - - public static T DeserializeFromFile(string path) - { - return Deserialize(File.ReadAllText(path)); - } - - public static T Deserialize(string json) - { - return JsonSerializer.Deserialize(json, GetDefaultSerializerOptions()); - } - - public static void Serialize(Stream stream, TValue obj, bool prettyPrint = false) - { - using (BinaryWriter writer = new BinaryWriter(stream)) - { - writer.Write(SerializeToUtf8Bytes(obj, prettyPrint)); - } - } - - public static string Serialize(TValue obj, bool prettyPrint = false) - { - return JsonSerializer.Serialize(obj, GetDefaultSerializerOptions(prettyPrint)); - } - - public static byte[] SerializeToUtf8Bytes(T obj, bool prettyPrint = false) - { - return JsonSerializer.SerializeToUtf8Bytes(obj, GetDefaultSerializerOptions(prettyPrint)); - } } -} +} \ No newline at end of file diff --git a/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs b/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs new file mode 100644 index 00000000..c0127dc4 --- /dev/null +++ b/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs @@ -0,0 +1,34 @@ +#nullable enable +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Ryujinx.Common.Utilities +{ + /// + /// Specifies that value of will be serialized as string in JSONs + /// + /// + /// Trimming friendly alternative to . + /// Get rid of this converter if dotnet supports similar functionality out of the box. + /// + /// Type of enum to serialize + public sealed class TypedStringEnumConverter : JsonConverter where TEnum : struct, Enum + { + public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var enumValue = reader.GetString(); + if (string.IsNullOrEmpty(enumValue)) + { + return default; + } + + return Enum.Parse(enumValue); + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} -- cgit v1.2.3