-
Notifications
You must be signed in to change notification settings - Fork 366
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1885 from Wibble199/feature/autojsonnode
Automatic JSON nodes
- Loading branch information
Showing
32 changed files
with
310 additions
and
436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Linq.Expressions; | ||
using System.Reflection; | ||
using static System.Linq.Expressions.Expression; | ||
|
||
namespace Aurora.Profiles { | ||
|
||
/// <summary> | ||
/// A version of <see cref="Node{TClass}"/> which automatically populates the fields defined on it from the parsed JSON data. | ||
/// </summary> | ||
public class AutoJsonNode<TSelf> : Node<TSelf> where TSelf : AutoJsonNode<TSelf> { | ||
// Did consider implementing this auto feature as a Fody weaver however, should profiles become plugin-based, each plugin would need to use Fody if they | ||
// wished to have the automatic capability. Doing it as a class that can be extended means that no additional setup is required for plugin authors. | ||
|
||
public AutoJsonNode() : base() { } | ||
public AutoJsonNode(string json) : base(json) { | ||
ctorAction.Value((TSelf)this); | ||
} | ||
|
||
#region Constructor builder | ||
// Compiled action to be run during the contructor that will populate relevant fields | ||
private static readonly Lazy<Action<TSelf>> ctorAction = new Lazy<Action<TSelf>>(() => { | ||
var fields = typeof(TSelf).GetFields(bf | BindingFlags.FlattenHierarchy); | ||
var body = new List<Expression>(fields.Length); | ||
var selfParam = Parameter(typeof(TSelf)); | ||
|
||
// Find all the fields | ||
foreach (var field in fields.Where(f => f.GetCustomAttribute<AutoJsonIgnoreAttribute>() == null)) { | ||
if (TryGetMethodForType(field.FieldType, out var getter)) | ||
// If a relevant Getter method exists for this field, add an assignment to the ctor body for this (e.g. adding `this.SomeField = GetString("SomeField");` ) | ||
body.Add( | ||
Assign( | ||
Field(selfParam, field), | ||
Call(selfParam, getter, Constant(field.GetCustomAttribute<AutoJsonPropertyNameAttribute>()?.Path ?? field.Name)) | ||
) | ||
); | ||
else | ||
Global.logger.Warn($"Could not find an AutoNode getter method for field '{field.Name}' of type '{field.FieldType.Name}'. It will not be automatically populated."); | ||
} | ||
|
||
// Compile and return the action | ||
return Lambda<Action<TSelf>>(Block(body), selfParam).Compile(); | ||
}); | ||
#endregion | ||
|
||
#region Getter methods | ||
private static BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; | ||
|
||
// An auto-generated list of get methods on Node<TSelf> | ||
private static readonly Dictionary<Type, MethodInfo> methods = typeof(TSelf).GetMethods(bf) | ||
// Only count methods that return something, take a single string parameter and whose names start with Get. E.G. bool GetBool(string name) would be a method that is returned | ||
.Where(m => !m.IsSpecialName && m.Name.StartsWith("Get") && m.ReturnType != typeof(void) && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(string)) | ||
.ToDictionary(m => m.ReturnType, m => m); | ||
|
||
// Special methods (with signatures that don't match the others) | ||
private static readonly MethodInfo arrayMethod = typeof(TSelf).GetMethod("GetArray", bf); | ||
private static readonly MethodInfo enumMethod = typeof(TSelf).GetMethod("GetEnum", bf); | ||
|
||
/// <summary> | ||
/// Tries to get the relevant get method on <see cref="Node{TClass}"/> for the given data type. Returns false if no method found.<para/> | ||
/// Examples:<code> | ||
/// GetMethodForType(typeof(string)); // returns 'GetString'<br/> | ||
/// GetMethodForType(typeof(SomeEnum)); // returns the closed generic variant of 'GetEnum<T>' bound to the given enum. | ||
/// </code></summary> | ||
private static bool TryGetMethodForType(Type type, out MethodInfo method) { | ||
if (type.IsEnum) | ||
method = enumMethod.MakeGenericMethod(type); | ||
else if (type.IsArray) | ||
method = arrayMethod.MakeGenericMethod(type.GetElementType()); | ||
else if (methods.TryGetValue(type, out var mi)) | ||
method = mi; | ||
else | ||
method = null; | ||
return method != null; | ||
} | ||
#endregion | ||
} | ||
|
||
|
||
#region Attributes | ||
/// <summary> | ||
/// Attribute to mark a field to indicate that the <see cref="AutoJsonNode{TSelf}"/> should use a different path when accessing the JSON. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] | ||
public class AutoJsonPropertyNameAttribute : Attribute { | ||
public string Path { get; set; } | ||
public AutoJsonPropertyNameAttribute(string path) { | ||
Path = path ?? throw new ArgumentNullException(nameof(path)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Attribute to mark a field to indicate that the <see cref="AutoJsonNode{TSelf}"/> should ignore this field when populating the class members. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] | ||
public class AutoJsonIgnoreAttribute : Attribute { } | ||
#endregion | ||
} |
1 change: 1 addition & 0 deletions
1
Project-Aurora/Project-Aurora/Profiles/Discord/GSI/GameState_Discord.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 0 additions & 18 deletions
18
Project-Aurora/Project-Aurora/Profiles/Discord/GSI/Nodes/ProviderNode.cs
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
Project-Aurora/Project-Aurora/Profiles/Generic/ProviderNode.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Aurora.Profiles.Generic.GSI.Nodes { | ||
|
||
public class ProviderNode : AutoJsonNode<ProviderNode> { | ||
|
||
public string Name; | ||
public int AppID; | ||
|
||
internal ProviderNode(string json) : base(json) { } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 0 additions & 18 deletions
18
Project-Aurora/Project-Aurora/Profiles/Minecraft/GSI/Nodes/ProviderNode.cs
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.