From 654e14bab47728692b0e1d00dc800b396eefca62 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 21 Aug 2022 21:14:18 +0800 Subject: [PATCH 1/3] Adjust the generic type convertor to let this shit accept different format of type such as enum. --- .../Converters/GenericTypeConvertor.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs index ace3d1fe4..ad2d105e8 100644 --- a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs @@ -10,7 +10,13 @@ namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters { - public abstract class GenericTypeConvertor : JsonConverter + public abstract class GenericTypeConvertor : GenericTypeConvertor + { + protected override string GetNameByType(MemberInfo type) + => type.Name; + } + + public abstract class GenericTypeConvertor : JsonConverter where TTypeName : notnull { public sealed override TType ReadJson(JsonReader reader, Type objectType, TType? existingValue, bool hasExistingValue, JsonSerializer serializer) { @@ -27,9 +33,13 @@ public sealed override TType ReadJson(JsonReader reader, Type objectType, TType? Type getTypeByProperties(IEnumerable properties) { - string? elementType = properties.FirstOrDefault(x => x.Name == "$type")?.Value.ToObject(); + var value = properties.FirstOrDefault(x => x.Name == "$type")?.Value; + if (value == null) + throw new ArgumentNullException(nameof(value)); + + TTypeName? elementType = value.ToObject(); if (elementType == null) - throw new ArgumentNullException(nameof(elementType)); + throw new InvalidCastException(nameof(elementType)); return GetTypeByName(elementType); } @@ -61,9 +71,8 @@ public sealed override void WriteJson(JsonWriter writer, TType? value, JsonSeria protected virtual void InteractWithJObject(JObject jObject, JsonWriter writer, TType value, JsonSerializer serializer) { } - protected abstract Type GetTypeByName(string name); + protected abstract Type GetTypeByName(TTypeName name); - protected virtual string GetNameByType(MemberInfo type) - => type.Name; + protected abstract TTypeName GetNameByType(MemberInfo type); } } From 38bde4036956827bfc4bca7b04b1d254261ecb47 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 21 Aug 2022 21:16:41 +0800 Subject: [PATCH 2/3] let the `KaraokeSkinElementConvertor` to inherit the `GenericTypeConvertor`. --- .../KaraokeSkinElementConvertorTest.cs | 10 ++++ .../Converters/KaraokeSkinElementConvertor.cs | 58 +++---------------- 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/KaraokeSkinElementConvertorTest.cs b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/KaraokeSkinElementConvertorTest.cs index 62bb27f99..f06a54179 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/KaraokeSkinElementConvertorTest.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/IO/Serialization/Converters/KaraokeSkinElementConvertorTest.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.IO.Serialization; using osu.Game.Rulesets.Karaoke.IO.Serialization.Converters; using osu.Game.Rulesets.Karaoke.Skinning.Elements; @@ -11,6 +12,15 @@ namespace osu.Game.Rulesets.Karaoke.Tests.IO.Serialization.Converters { public class KaraokeSkinElementConvertorTest : BaseSingleConverterTest { + protected override JsonConverter[] CreateExtraConverts() + => new JsonConverter[] + { + new ColourConvertor(), + new Vector2Converter(), + new ShaderConvertor(), + new FontUsageConvertor() + }; + [Test] public void TestLyricConfigSerializer() { diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/KaraokeSkinElementConvertor.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/KaraokeSkinElementConvertor.cs index 9d84fc698..3d92b5f06 100644 --- a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/KaraokeSkinElementConvertor.cs +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/KaraokeSkinElementConvertor.cs @@ -2,64 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using osu.Framework.IO.Serialization; -using osu.Game.IO.Serialization; +using System.Reflection; using osu.Game.Rulesets.Karaoke.Skinning.Elements; namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters { - public class KaraokeSkinElementConvertor : JsonConverter + public class KaraokeSkinElementConvertor : GenericTypeConvertor { - // because we wants serializer that containers some common convertors except this one, so make a local one. - private readonly JsonSerializer localSerializer; + protected override Type GetTypeByName(ElementType name) + => GetObjectType(name); - public KaraokeSkinElementConvertor() - { - var settings = JsonSerializableExtensions.CreateGlobalSettings(); - settings.ContractResolver = new KaraokeSkinContractResolver(); - settings.Converters.Add(new ColourConvertor()); - settings.Converters.Add(new Vector2Converter()); - settings.Converters.Add(new ShaderConvertor()); - settings.Converters.Add(new FontUsageConvertor()); - localSerializer = JsonSerializer.Create(settings); - } + protected override ElementType GetNameByType(MemberInfo type) + => GetElementType(type); - public override IKaraokeSkinElement? ReadJson(JsonReader reader, Type objectType, IKaraokeSkinElement? existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var jObject = JObject.Load(reader); - var properties = jObject.Children().OfType().ToArray(); - - var type = objectType != typeof(IKaraokeSkinElement) ? objectType : getTypeByProperties(properties); - var newReader = jObject.CreateReader(); - return localSerializer.Deserialize(newReader, type) as IKaraokeSkinElement; - - static Type getTypeByProperties(IEnumerable properties) - { - var elementType = properties.FirstOrDefault(x => x.Name == "$type")?.Value.ToObject(); - if (elementType == null) - throw new ArgumentNullException(nameof(elementType)); - - return GetObjectType(elementType.Value); - } - } - - public override void WriteJson(JsonWriter writer, IKaraokeSkinElement? value, JsonSerializer serializer) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - var jObject = JObject.FromObject(value, localSerializer); - - // should get type from enum instead of class type because change class name might cause resource not found. - jObject.AddFirst(new JProperty("$type", GetElementType(value.GetType()))); - jObject.WriteTo(writer); - } - - public static ElementType GetElementType(Type elementType) => + public static ElementType GetElementType(MemberInfo elementType) => elementType switch { var type when type == typeof(LyricConfig) => ElementType.LyricConfig, From 94c69aa31d2de9f55338ac19e1d88b5164f17206 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 21 Aug 2022 21:19:39 +0800 Subject: [PATCH 3/3] Rename the resolver. And mark that this resolver is referenced by https://stackoverflow.com/a/18548894. --- .../IO/Serialization/Converters/GenericTypeConvertor.cs | 2 +- ...kinContractResolver.cs => WritablePropertiesOnlyResolver.cs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Karaoke/IO/Serialization/{KaraokeSkinContractResolver.cs => WritablePropertiesOnlyResolver.cs} (91%) diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs index ad2d105e8..ccf90c71b 100644 --- a/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/Converters/GenericTypeConvertor.cs @@ -57,7 +57,7 @@ public sealed override void WriteJson(JsonWriter writer, TType? value, JsonSeria // follow: https://stackoverflow.com/a/59329703 // not a good way but seems there's no better choice. serializer.Converters.Remove(this); - serializer.ContractResolver = new KaraokeSkinContractResolver(); + serializer.ContractResolver = new WritablePropertiesOnlyResolver(); var jObject = JObject.FromObject(value, serializer); diff --git a/osu.Game.Rulesets.Karaoke/IO/Serialization/KaraokeSkinContractResolver.cs b/osu.Game.Rulesets.Karaoke/IO/Serialization/WritablePropertiesOnlyResolver.cs similarity index 91% rename from osu.Game.Rulesets.Karaoke/IO/Serialization/KaraokeSkinContractResolver.cs rename to osu.Game.Rulesets.Karaoke/IO/Serialization/WritablePropertiesOnlyResolver.cs index 08a6f0a51..ed27ad09c 100644 --- a/osu.Game.Rulesets.Karaoke/IO/Serialization/KaraokeSkinContractResolver.cs +++ b/osu.Game.Rulesets.Karaoke/IO/Serialization/WritablePropertiesOnlyResolver.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Karaoke.IO.Serialization /// /// This contract resolver is for save and load data from /// - public class KaraokeSkinContractResolver : SnakeCaseKeyContractResolver + public class WritablePropertiesOnlyResolver : SnakeCaseKeyContractResolver { // we only wants to save properties that only writable. protected override IList CreateProperties(Type type, MemberSerialization memberSerialization)