Skip to content

Commit

Permalink
Merge pull request #1520 from andy840119/implement-generic-type-conve…
Browse files Browse the repository at this point in the history
…rtor

Implement generic type convertor.
  • Loading branch information
andy840119 authored Aug 21, 2022
2 parents 8f5d600 + 43a1b18 commit 3645027
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 183 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) andy840119 <[email protected]>. Licensed under the GPL Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters
{
public abstract class GenericTypeConvertor<TType> : JsonConverter<TType>
{
public sealed override TType ReadJson(JsonReader reader, Type objectType, TType? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var jProperties = jObject.Children().OfType<JProperty>().ToArray();
var type = objectType != typeof(TType) ? objectType : getTypeByProperties(jProperties);

var newReader = jObject.CreateReader();

var instance = (TType)Activator.CreateInstance(type);
InteractWithJObject(jObject, objectType, existingValue, hasExistingValue, serializer);
serializer.Populate(newReader, instance);
return instance;

Type getTypeByProperties(IEnumerable<JProperty> properties)
{
string? elementType = properties.FirstOrDefault(x => x.Name == "$type")?.Value.ToObject<string>();
if (elementType == null)
throw new ArgumentNullException(nameof(elementType));

return GetTypeByName(elementType);
}
}

protected virtual void InteractWithJObject(JObject jObject, Type objectType, TType? existingValue, bool hasExistingValue, JsonSerializer serializer) { }

public sealed override void WriteJson(JsonWriter writer, TType? value, JsonSerializer serializer)
{
if (value == null)
throw new ArgumentNullException(nameof(value));

var resolver = serializer.ContractResolver;

// 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();

var jObject = JObject.FromObject(value, serializer);

serializer.Converters.Add(this);
serializer.ContractResolver = resolver;

InteractWithJObject(jObject, writer, value, serializer);
jObject.AddFirst(new JProperty("$type", GetNameByType(value.GetType())));
jObject.WriteTo(writer);
}

protected virtual void InteractWithJObject(JObject jObject, JsonWriter writer, TType value, JsonSerializer serializer) { }

protected abstract Type GetTypeByName(string name);

protected virtual string GetNameByType(MemberInfo type)
=> type.Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,18 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Karaoke.Skinning.Groups;

namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters
{
public class KaraokeSkinGroupConvertor : JsonConverter<IGroup>
public class KaraokeSkinGroupConvertor : GenericTypeConvertor<IGroup>
{
// because we wants serializer that containers some common convertors except this one, so make a local one.
private readonly JsonSerializer localSerializer;

public KaraokeSkinGroupConvertor()
{
var settings = JsonSerializableExtensions.CreateGlobalSettings();
settings.ContractResolver = new KaraokeSkinContractResolver();
localSerializer = JsonSerializer.Create(settings);
}

public override IGroup? ReadJson(JsonReader reader, Type objectType, IGroup? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var properties = jObject.Children().OfType<JProperty>().ToArray();

var type = objectType != typeof(IGroup) ? objectType : getTypeByProperties(properties);
var newReader = jObject.CreateReader();
return localSerializer.Deserialize(newReader, type) as IGroup;

static Type getTypeByProperties(IEnumerable<JProperty> properties)
{
string? elementType = properties.FirstOrDefault(x => x.Name == "$type")?.Value.ToObject<string>();
if (elementType == null)
throw new ArgumentNullException(nameof(elementType));

return getTypeByName(elementType);
}
}

public override void WriteJson(JsonWriter writer, IGroup? 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", getNameByType(value.GetType())));
jObject.WriteTo(writer);
}

private static Type getTypeByName(string name)
protected override Type GetTypeByName(string name)
{
// only get name from font
var assembly = Assembly.GetExecutingAssembly();
return assembly.GetType($"osu.Game.Rulesets.Karaoke.Skinning.Groups.{name}");
}

private static string getNameByType(MemberInfo type)
=> type.Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,18 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Karaoke.Skinning.MappingRoles;

namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters
{
public class KaraokeSkinMappingRoleConvertor : JsonConverter<IMappingRole>
public class KaraokeSkinMappingRoleConvertor : GenericTypeConvertor<IMappingRole>
{
// because we wants serializer that containers some common convertors except this one, so make a local one.
private readonly JsonSerializer localSerializer;

public KaraokeSkinMappingRoleConvertor()
{
var settings = JsonSerializableExtensions.CreateGlobalSettings();
settings.ContractResolver = new KaraokeSkinContractResolver();
localSerializer = JsonSerializer.Create(settings);
}

public override IMappingRole? ReadJson(JsonReader reader, Type objectType, IMappingRole? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
var properties = jObject.Children().OfType<JProperty>().ToArray();

var type = objectType != typeof(IMappingRole) ? objectType : getTypeByProperties(properties);
var newReader = jObject.CreateReader();
return localSerializer.Deserialize(newReader, type) as IMappingRole;

static Type getTypeByProperties(IEnumerable<JProperty> properties)
{
string? elementType = properties.FirstOrDefault(x => x.Name == "$type")?.Value.ToObject<string>();
if (elementType == null)
throw new ArgumentNullException(nameof(elementType));

return getTypeByName(elementType);
}
}

public override void WriteJson(JsonWriter writer, IMappingRole? 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", getNameByType(value.GetType())));
jObject.WriteTo(writer);
}

private static Type getTypeByName(string name)
protected override Type GetTypeByName(string name)
{
// only get name from font
var assembly = Assembly.GetExecutingAssembly();
return assembly.GetType($"osu.Game.Rulesets.Karaoke.Skinning.MappingRoles.{name}");
}

private static string getNameByType(MemberInfo type)
=> type.Name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,89 +2,18 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using osu.Framework.Graphics.Shaders;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Karaoke.Utils;

namespace osu.Game.Rulesets.Karaoke.IO.Serialization.Converters
{
public class ShaderConvertor : JsonConverter<ICustomizedShader>
public class ShaderConvertor : GenericTypeConvertor<ICustomizedShader>
{
// because we wants serializer that containers some common convertors except this one, so make a local one.
private readonly JsonSerializer localSerializer;

public ShaderConvertor()
{
var settings = JsonSerializableExtensions.CreateGlobalSettings();
settings.ContractResolver = new KaraokeSkinContractResolver();
settings.Converters.Add(new ColourConvertor());
localSerializer = JsonSerializer.Create(settings);
}

public override ICustomizedShader? ReadJson(JsonReader reader, Type objectType, ICustomizedShader? existingValue, bool hasExistingValue, JsonSerializer serializer)
protected override void InteractWithJObject(JObject jObject, JsonWriter writer, ICustomizedShader value, JsonSerializer serializer)
{
// if not knows the type, then should get the type from type field and re-deserializer again.
var jObject = JObject.Load(reader);
var properties = jObject.Children().OfType<JProperty>().ToArray();

// should process child shader first if have.
var childShaders = getShadersFromProperties(properties, serializer);

if (childShaders != null)
{
// remove value in this field, but not remove the property.
jObject.Remove("step_shaders");
jObject.Add("step_shaders", new JArray());
}

// should create new reader because old reader cannot reset read position.
var newReader = jObject.CreateReader();
var type = objectType != typeof(ICustomizedShader) ? objectType : getTypeByProperties(properties);
var shader = localSerializer.Deserialize(newReader, type) as ICustomizedShader;

if (shader is StepShader stepShader && childShaders != null)
{
stepShader.StepShaders = childShaders;
}

return shader;

static Type? getTypeByProperties(IEnumerable<JProperty> properties)
{
string? typeString = properties.FirstOrDefault(x => x.Name == "$type")?.Value.ToObject<string>();
if (string.IsNullOrEmpty(typeString))
return default;

return getTypeByName(typeString);
}

static ICustomizedShader[]? getShadersFromProperties(IEnumerable<JProperty> properties, JsonSerializer serializer)
{
// deserialize step shaders if process step shaders.
var stepShaders = properties.FirstOrDefault(x => x.Name == "step_shaders");

if (stepShaders == null)
return null;

var shaderReader = stepShaders.Value.CreateReader();
var shaders = serializer.Deserialize<ICustomizedShader[]>(shaderReader);
return shaders;
}
}

public override void WriteJson(JsonWriter writer, ICustomizedShader? value, JsonSerializer serializer)
{
if (value == null)
throw new ArgumentNullException(nameof(value));

var jObject = JObject.FromObject(value, localSerializer);
jObject.AddFirst(new JProperty("$type", getNameByType(value.GetType())));

var childShader = getShadersFromParent(value, serializer);

if (childShader != null)
Expand All @@ -93,8 +22,6 @@ public override void WriteJson(JsonWriter writer, ICustomizedShader? value, Json
jObject.Add("step_shaders", childShader);
}

jObject.WriteTo(writer);

static JArray? getShadersFromParent(ICustomizedShader shader, JsonSerializer serializer)
{
if (shader is not StepShader stepShader)
Expand All @@ -104,14 +31,12 @@ public override void WriteJson(JsonWriter writer, ICustomizedShader? value, Json
}
}

private static Type? getTypeByName(string name)
protected override Type GetTypeByName(string name)
{
// only get name from font
var assembly = AssemblyUtils.GetAssemblyByName("osu.Framework.KaraokeFont");
return assembly?.GetType($"osu.Framework.Graphics.Shaders.{name}");
Debug.Assert(assembly != null);
return assembly.GetType($"osu.Framework.Graphics.Shaders.{name}");
}

private static string getNameByType(MemberInfo type)
=> type.Name;
}
}

0 comments on commit 3645027

Please sign in to comment.