Skip to content
This repository has been archived by the owner on Apr 8, 2024. It is now read-only.

Commit

Permalink
Merge pull request #311 from aspriddell/jsonpath-internal
Browse files Browse the repository at this point in the history
change modern stats JPath handling
  • Loading branch information
aspriddell authored Apr 7, 2022
2 parents ae4f679 + ce55d43 commit cd04c1f
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 65 deletions.
2 changes: 1 addition & 1 deletion DragonFruit.Six.Api.Tests/Data/ModernStatsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async Task TestModernSummary(string userId, Platform platform)
var data = await Client.GetModernStatsSummaryAsync(account);

// summaries are returned in an array but there should only be one entry
data?.AllModes.AsAny.SingleOrDefault();
var stats = data?.AllModes.AsAny.SingleOrDefault();
}

[TestCase("14c01250-ef26-4a32-92ba-e04aa557d619", Platform.PC)]
Expand Down
27 changes: 9 additions & 18 deletions DragonFruit.Six.Api/Modern/Containers/ModernModeStatsContainer.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
// Dragon6 API Copyright DragonFruit Network <inbox@dragonfruit.network>
// Licensed under Apache-2. Refer to the LICENSE file for more info

using System;
using DragonFruit.Six.Api.Modern.Enums;
using DragonFruit.Six.Api.Modern.Utils;
using Newtonsoft.Json;

namespace DragonFruit.Six.Api.Modern.Containers
{
[JsonPathSerializable]
[JsonObject(MemberSerialization.OptIn)]
[JsonConverter(typeof(JsonPathConverter))]
public class ModernModeStatsContainer<T>
{
[JsonIgnore]
public ModernRoleStatsContainer<T> this[PlaylistType type] => type switch
{
PlaylistType.Casual => Casual,
PlaylistType.Ranked => Ranked,
PlaylistType.Unranked => Unranked,
PlaylistType.Independent => AllModes,

_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};

[JsonProperty("gameModes.all")]
[JsonProperty("all")]
[JsonPath("gameModes.all")]
public ModernRoleStatsContainer<T> AllModes { get; set; }

[JsonProperty("gameModes.casual")]
[JsonProperty("casual")]
[JsonPath("gameModes.casual")]
public ModernRoleStatsContainer<T> Casual { get; set; }

[JsonProperty("gameModes.ranked")]
[JsonProperty("ranked")]
[JsonPath("gameModes.ranked")]
public ModernRoleStatsContainer<T> Ranked { get; set; }

[JsonProperty("gameModes.unranked")]
[JsonProperty("unranked")]
[JsonPath("gameModes.unranked")]
public ModernRoleStatsContainer<T> Unranked { get; set; }
}
}
22 changes: 7 additions & 15 deletions DragonFruit.Six.Api/Modern/Containers/ModernRoleStatsContainer.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,25 @@
// Dragon6 API Copyright DragonFruit Network <inbox@dragonfruit.network>
// Licensed under Apache-2. Refer to the LICENSE file for more info

using System;
using DragonFruit.Six.Api.Enums;
using DragonFruit.Six.Api.Modern.Utils;
using Newtonsoft.Json;

namespace DragonFruit.Six.Api.Modern.Containers
{
[JsonPathSerializable]
[JsonObject(MemberSerialization.OptIn)]
[JsonConverter(typeof(JsonPathConverter))]
public class ModernRoleStatsContainer<T>
{
public T this[OperatorType type] => type switch
{
OperatorType.Independent => AsAny,
OperatorType.Attacker => AsAttacker,
OperatorType.Defender => AsDefender,

_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
};

[JsonProperty("teamRoles.all")]
[JsonProperty("all")]
[JsonPath("teamRoles.all")]
public T AsAny { get; set; }

[JsonProperty("teamRoles.attacker")]
[JsonProperty("attacker")]
[JsonPath("teamRoles.attacker")]
public T AsAttacker { get; set; }

[JsonProperty("teamRoles.defender")]
[JsonProperty("defender")]
[JsonPath("teamRoles.defender")]
public T AsDefender { get; set; }
}
}
8 changes: 5 additions & 3 deletions DragonFruit.Six.Api/Modern/Containers/WeaponSlot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@

namespace DragonFruit.Six.Api.Modern.Containers
{
[JsonPathSerializable]
[JsonObject(MemberSerialization.OptIn)]
[JsonConverter(typeof(JsonPathConverter))]
public class WeaponSlot
{
[JsonProperty("weaponSlots.primaryWeapons.weaponTypes")]
[JsonProperty("primary")]
[JsonPath("weaponSlots.primaryWeapons.weaponTypes")]
public IEnumerable<WeaponGroup> Primary { get; set; }

[JsonProperty("weaponSlots.secondaryWeapons.weaponTypes")]
[JsonProperty("secondary")]
[JsonPath("weaponSlots.secondaryWeapons.weaponTypes")]
public IEnumerable<WeaponGroup> Secondary { get; set; }
}
}
32 changes: 21 additions & 11 deletions DragonFruit.Six.Api/Modern/Entities/ModernStatsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

namespace DragonFruit.Six.Api.Modern.Entities
{
[JsonPathSerializable]
[JsonObject(MemberSerialization.OptIn)]
[JsonConverter(typeof(JsonPathConverter))]
public abstract class ModernStatsBase
{
private float? _roundWl, _matchWl, _kd;
Expand Down Expand Up @@ -117,37 +117,47 @@ public abstract class ModernStatsBase

#region Ratios

[JsonProperty("headshotAccuracy.value")]
[JsonProperty("headshotAccuracy")]
[JsonPath("headshotAccuracy.value")]
public float HeadshotAccuracy { get; set; }

[JsonProperty("killsPerRound.value")]
[JsonProperty("killsPerRound")]
[JsonPath("killsPerRound.value")]
public float KillsPerRound { get; set; }

[JsonProperty("roundsWithAKill.value")]
[JsonProperty("roundsWithAKill")]
[JsonPath("roundsWithAKill.value")]
public float RoundsWithSingleKill { get; set; }

[JsonProperty("roundsWithMultiKill.value")]
[JsonProperty("roundsWithMultiKill")]
[JsonPath("roundsWithMultiKill.value")]
public float RoundsWithMultipleKills { get; set; }

[JsonProperty("roundsWithOpeningKill.value")]
[JsonProperty("roundsWithOpeningKill")]
[JsonPath("roundsWithOpeningKill.value")]
public float RoundsWithFirstKill { get; set; }

[JsonProperty("roundsWithOpeningDeath.value")]
[JsonProperty("roundsWithOpeningDeath")]
[JsonPath("roundsWithOpeningDeath.value")]
public float RoundsWithFirstDeath { get; set; }

[JsonProperty("roundsSurvived.value")]
[JsonProperty("roundsSurvived")]
[JsonPath("roundsSurvived.value")]
public float RoundsSurvived { get; set; }

[JsonProperty("roundsWithAnAce.value")]
[JsonProperty("roundsWithAnAce")]
[JsonPath("roundsWithAnAce.value")]
public float RoundsAced { get; set; }

[JsonProperty("roundsWithClutch.value")]
[JsonProperty("roundsWithClutch")]
[JsonPath("roundsWithClutch.value")]
public float RoundsClutched { get; set; }

/// <summary>
/// Rounds with a kill, objective completion, survive or trade kills
/// </summary>
[JsonProperty("roundsWithKOST.value")]
[JsonProperty("roundsWithKOST")]
[JsonPath("roundsWithKOST.value")]
public float RoundsWithKillObjectiveSurvivalOrTrade { get; set; }

public float Kd => _kd ??= RatioUtils.RatioOf(Kills, Deaths);
Expand Down
6 changes: 5 additions & 1 deletion DragonFruit.Six.Api/Modern/ModernStatsDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using DragonFruit.Six.Api.Modern.Containers;
using DragonFruit.Six.Api.Modern.Requests;
using DragonFruit.Six.Api.Modern.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace DragonFruit.Six.Api.Modern
Expand All @@ -16,7 +17,10 @@ public static class ModernStatsDeserializer
public static ModernModeStatsContainer<T> ProcessData<T>(this JObject source, ModernStatsRequest request)
{
var data = source?["platforms"]?[request.Account.Platform.ModernName()];
return data?.ToObject<ModernModeStatsContainer<T>>();
return data?.ToObject<ModernModeStatsContainer<T>>(new JsonSerializer
{
Converters = { new JsonPathConverter() }
});
}
}
}
17 changes: 17 additions & 0 deletions DragonFruit.Six.Api/Modern/Utils/JsonPathAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Dragon6 API Copyright DragonFruit Network <inbox@dragonfruit.network>
// Licensed under Apache-2. Refer to the LICENSE file for more info

using System;

namespace DragonFruit.Six.Api.Modern.Utils
{
internal class JsonPathAttribute : Attribute
{
public JsonPathAttribute(string path)
{
Path = path;
}

public string Path { get; }
}
}
28 changes: 12 additions & 16 deletions DragonFruit.Six.Api/Modern/Utils/JsonPathConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@

using System;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

namespace DragonFruit.Six.Api.Modern.Utils
{
/// <summary>
/// A <see cref="JsonConverter"/> for traversing through multiple properties separated by a full stop
/// A <see cref="JsonConverter"/> for extracting elements based on a JPath expression
/// </summary>
/// <remarks>
/// Taken from https://automationrhapsody.com/partial-json-deserialize-jsonpath-json-net/
/// Portions taken from https://automationrhapsody.com/partial-json-deserialize-jsonpath-json-net/
/// </remarks>
internal class JsonPathConverter : JsonConverter
{
Expand All @@ -27,32 +28,27 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist

foreach (var prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite))
{
var jsonPropertyAttr = prop.GetCustomAttributes(true).OfType<JsonPropertyAttribute>().FirstOrDefault();
// allow fetching by path or by jsonproperty if there's no path
var path = prop.GetCustomAttribute<JsonPathAttribute>(true)?.Path ?? prop.GetCustomAttribute<JsonPropertyAttribute>(true)?.PropertyName;

if (jsonPropertyAttr == null)
if (path == null)
{
continue;
}

var token = jObject.SelectToken(jsonPropertyAttr.PropertyName);
var token = jObject.SelectToken(path);

if (token == null || token.Type == JTokenType.Null)
{
continue;
}

var jsonConverterAttr = prop.GetCustomAttributes(true).OfType<JsonConverterAttribute>().FirstOrDefault();
var jsonConverterAttr = prop.GetCustomAttribute<JsonConverterAttribute>(true);
object value;

if (jsonConverterAttr == null)
{
serializer.Converters.Clear();
value = token.ToObject(prop.PropertyType, serializer);
}
else
{
value = JsonConvert.DeserializeObject(token.ToString(), prop.PropertyType, (JsonConverter)Activator.CreateInstance(jsonConverterAttr.ConverterType));
}
value = jsonConverterAttr == null
? token.ToObject(prop.PropertyType, serializer)
: JsonConvert.DeserializeObject(token.ToString(), prop.PropertyType, (JsonConverter)Activator.CreateInstance(jsonConverterAttr.ConverterType));

prop.SetValue(targetObj, value, null);
}
Expand All @@ -68,7 +64,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
return targetObj;
}

public override bool CanConvert(Type objectType) => true;
public override bool CanConvert(Type objectType) => !objectType.IsInterface && objectType.GetCustomAttribute<JsonPathSerializableAttribute>() is not null;

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException();
}
Expand Down
14 changes: 14 additions & 0 deletions DragonFruit.Six.Api/Modern/Utils/JsonPathSerializableAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Dragon6 API Copyright DragonFruit Network <inbox@dragonfruit.network>
// Licensed under Apache-2. Refer to the LICENSE file for more info

using System;

namespace DragonFruit.Six.Api.Modern.Utils
{
/// <summary>
/// Marks a class as being able to be deserialized by path reference
/// </summary>
internal class JsonPathSerializableAttribute : Attribute
{
}
}

0 comments on commit cd04c1f

Please sign in to comment.