Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.Text.Json support. #101

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e285e2e
WIP STJ Support
Hawxy Jul 29, 2022
059bd4b
Handle null flag
Hawxy Jul 29, 2022
be3c10e
Fix build
Hawxy Jul 29, 2022
bfe1215
Support optional
Hawxy Jul 29, 2022
9d64b79
Fixed and expanded upon existing code.
Quahu Aug 1, 2022
8a34d12
Added some TODOs.
Quahu Aug 1, 2022
00cf546
Implemented STJ
Quahu Jul 29, 2024
9c053b9
Fix STJ polymorphism issue
Quahu Jul 29, 2024
69859ca
Add extension data support
Quahu Jul 29, 2024
6c3b560
Fix ToJsonString() in dispatch error log
Quahu Jul 29, 2024
a90ed07
API adjustments
Quahu Jul 29, 2024
e23c621
Fix extension data not being deserialized
Quahu Jul 30, 2024
cc60082
More adjustments
Quahu Jul 30, 2024
4b6c07b
Make nodes internal
Quahu Jul 30, 2024
c20c720
Refactor converters
Quahu Jul 30, 2024
82389a4
Disable STJ impl for .NET 6
Quahu Jul 30, 2024
5d291be
Add conditional serializer replacement to TestBot
Quahu Jul 30, 2024
246e56b
Made STJ nodes fully internal
Quahu Jul 30, 2024
12c283f
Fix TestBot build
Quahu Jul 30, 2024
2f9d927
Fix BindOptions for STJ. API adjustments
Quahu Aug 14, 2024
46e5bd4
Add Type to IJsonNode
Quahu Aug 24, 2024
4a9fb46
Fix VOICE_STATE_UPDATE lurker check
Quahu Aug 24, 2024
e0a0933
Ensure GUILD_CREATE and GUILD_DELETE pop pending guild even on failure
Quahu Aug 24, 2024
809485c
Add Path to IJsonNode
Quahu Aug 24, 2024
bd81efe
Fix loss of JSON information in converter exceptions
Quahu Aug 24, 2024
63d9c6f
Add common extension for deserializing JSON arrays
Quahu Aug 24, 2024
28cd56b
Safely deserialize gateway guild's members
Quahu Aug 24, 2024
a60ebfa
Update STJ
Quahu Aug 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Disqord.Serialization.Json;
using Qmmands;
using Qommon;

Expand Down Expand Up @@ -84,15 +85,17 @@ static IReadOnlyDictionary<string, ISlashCommandInteractionOption> GetArguments(
continue;
}

if (option.Value is not string stringValue)
if (option.Value.Type != JsonValueType.String)
{
var value = GetOptionValue(option);
arguments[parameter] = actualType.IsEnum
? Enum.ToObject(actualType, option.Value)
: Convert.ChangeType(option.Value, actualType, context.Locale);
? Enum.ToObject(actualType, value)
: Convert.ChangeType(value, actualType, context.Locale);

continue;
}

var stringValue = option.Value.ToType<string>()!;
if (interaction is IAutoCompleteInteraction)
{
// Treat string values as arguments for auto-complete.
Expand All @@ -105,7 +108,7 @@ static IReadOnlyDictionary<string, ISlashCommandInteractionOption> GetArguments(
{
// If the option is just a string, pass it through to type parsing.
var rawArguments = context.RawArguments ??= new Dictionary<IParameter, MultiString>();
rawArguments[parameter] = option.Value as string;
rawArguments[parameter] = stringValue;
continue;
}

Expand Down Expand Up @@ -134,5 +137,30 @@ static IReadOnlyDictionary<string, ISlashCommandInteractionOption> GetArguments(

return Next.ExecuteAsync(context);
}

private static object GetOptionValue(ISlashCommandInteractionOption option)
{
var value = option.Value!;
switch (option.Type)
{
case SlashCommandOptionType.Integer:
{
return value.ToType<long>();
}
case SlashCommandOptionType.Boolean:
{
return value.ToType<bool>();
}
case SlashCommandOptionType.Number:
{
return value.ToType<double>();
}
default:
{
Throw.ArgumentOutOfRangeException(nameof(option));
return default;
}
}
}
}
}
8 changes: 8 additions & 0 deletions src/Disqord.Core/Disqord.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.1"/>
<PackageReference Include="Qommon" Version="4.0.1"/>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<Compile Remove="Serialization/Json/STJ/**"/>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' != 'net6.0' ">
<PackageReference Include="System.Text.Json" Version="9.0.0-preview.7.24405.7"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Disqord.Serialization.Json;

namespace Disqord;

Expand All @@ -23,7 +24,7 @@ public interface ISlashCommandInteractionOption
/// <remarks>
/// <see cref="Value"/> and <see cref="Options"/> are mutually exclusive.
/// </remarks>
object? Value { get; }
IJsonValue? Value { get; }

/// <summary>
/// Gets the nested options of this option.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public IReadOnlyDictionary<CultureInfo, string> NameLocalizations
}

/// <inheritdoc/>
public object Value => Model.Value.Value!;
public object Value => Model.Value.ToType<object>()!;

public TransientSlashCommandOptionChoice(IClient client, ApplicationCommandOptionChoiceJsonModel model)
: base(client, model)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Disqord.Models;
using Disqord.Serialization.Json;
using Qommon;
using Qommon.Collections.ReadOnly;

Expand All @@ -15,7 +16,7 @@ public class TransientSlashCommandInteractionOption : TransientClientEntity<Appl
public SlashCommandOptionType Type => Model.Type;

/// <inheritdoc/>
public object? Value => Model.Value.GetValueOrDefault()?.Value;
public IJsonValue? Value => Model.Value.GetValueOrDefault();

/// <inheritdoc/>
public IReadOnlyDictionary<string, ISlashCommandInteractionOption> Options
Expand Down
2 changes: 2 additions & 0 deletions src/Disqord.Core/Library/Library.Debug.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public static partial class Library
[EditorBrowsable(EditorBrowsableState.Never)]
public static class Debug
{
public static bool LogSafeDeserializationExceptions;

public static bool DumpJson;

public static TextWriter DumpWriter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ protected override void OnValidate()
Guard.HasSizeBetweenOrEqualTo(Name, Discord.Limits.ApplicationCommand.Option.Choice.MinNameLength, Discord.Limits.ApplicationCommand.Option.Choice.MaxNameLength);

Guard.IsNotNull(Value);
Guard.IsNotNull(Value.Value);

var value = Guard.IsAssignableToType<IConvertible>(Value.Value, nameof(Value));
var objectValue = Value.ToType<object>();
Guard.IsNotNull(objectValue);

var value = Guard.IsAssignableToType<IConvertible>(objectValue, nameof(Value));
switch (value.GetTypeCode())
{
case TypeCode.SByte:
Expand Down Expand Up @@ -62,4 +64,4 @@ protected override void OnValidate()
}
}
}
}
}
38 changes: 38 additions & 0 deletions src/Disqord.Core/Models/Extensions/JsonArrayExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Disqord.Serialization.Json;
using Microsoft.Extensions.Logging;

namespace Disqord.Models;

internal static class JsonArrayExtensions
{
public static IEnumerable<TJsonModel> SafelyDeserializeItems<TJsonModel>(this IJsonArray jsonArray, ILogger logger, [CallerArgumentExpression(nameof(jsonArray))] string? caller = null)
where TJsonModel : JsonModel
{
var count = jsonArray.Count;
for (var i = 0; i < count; i++)
{
TJsonModel? model = null;
try
{
model = jsonArray[i]?.ToType<TJsonModel>();
}
catch (Exception ex)
{
if (Library.Debug.LogSafeDeserializationExceptions)
{
logger.LogWarning(ex, "Failed to deserialize {Caller}.", caller);
}
}

if (model == null)
{
continue;
}

yield return model;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ public virtual void Serialize(Stream stream, object obj, IJsonSerializerOptions?
public virtual IJsonNode GetJsonNode(object? obj)
{
if (obj == null)
{
return DefaultJsonNode.Create(JValue.CreateNull(), UnderlyingSerializer);
}

return DefaultJsonNode.Create(JToken.FromObject(obj, UnderlyingSerializer), UnderlyingSerializer);
}
Expand Down Expand Up @@ -145,4 +147,4 @@ public static JsonTextWriter Conditional(IJsonSerializerOptions? options, TextWr
return new JsonTextWriter(writer);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ protected override JsonObjectContract CreateObjectContract(Type objectType)
{
null => null,
JToken jToken => DefaultJsonNode.Create(jToken, _serializer.UnderlyingSerializer),
_ => DefaultJsonNode.Create(JToken.FromObject(value, _serializer.UnderlyingSerializer), _serializer.UnderlyingSerializer)
_ => DefaultJsonNode.Create(value, _serializer.UnderlyingSerializer)
};

model.ExtensionData.Add(key, node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,70 @@ public class DefaultJsonArray : DefaultJsonNode, IJsonArray
public int Count => Token.Count;

/// <inheritdoc/>
public IJsonNode? this[int index] => Create(Token[index], Serializer);
public IJsonNode? this[int index]
{
get => Create(Token[index], Serializer);
set => Token[index] = GetJToken(value)!;
}

bool ICollection<IJsonNode?>.IsReadOnly => false;

public DefaultJsonArray(JArray token, JsonSerializer serializer)
: base(token, serializer)
{ }

/// <inheritdoc/>
public void Add(IJsonNode? item)
{
Token.Add(GetJToken(item)!);
}

/// <inheritdoc/>
public void Clear()
{
Token.Clear();
}

/// <inheritdoc/>
public bool Contains(IJsonNode? item)
{
return Token.Contains(GetJToken(item)!);
}

/// <inheritdoc/>
public void CopyTo(IJsonNode?[] array, int arrayIndex)
{
var count = Count;
for (var i = 0; i < count; i++)
{
array[arrayIndex + i] = this[i];
}
}

/// <inheritdoc/>
public bool Remove(IJsonNode? item)
{
return Token.Remove(GetJToken(item)!);
}

/// <inheritdoc/>
public int IndexOf(IJsonNode? item)
{
return Token.IndexOf(GetJToken(item)!);
}

/// <inheritdoc/>
public void Insert(int index, IJsonNode? item)
{
Token.Insert(index, GetJToken(item)!);
}

/// <inheritdoc/>
public void RemoveAt(int index)
{
Token.RemoveAt(index);
}

/// <inheritdoc/>
public IEnumerator<IJsonNode?> GetEnumerator()
{
Expand Down Expand Up @@ -72,4 +130,4 @@ public void Reset()
public void Dispose()
{ }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Qommon;

namespace Disqord.Serialization.Json.Default;

Expand All @@ -16,7 +17,25 @@ public class DefaultJsonNode : IJsonNode
/// </summary>
public JToken Token { get; }

private protected readonly JsonSerializer Serializer;
/// <summary>
/// Gets the underlying serializer.
/// </summary>
public JsonSerializer Serializer { get; }

/// <inheritdoc/>
public string Path => Token.Path;

/// <inheritdoc/>
public JsonValueType Type => Token.Type switch
{
JTokenType.Object => JsonValueType.Object,
JTokenType.Array => JsonValueType.Array,
JTokenType.Integer or JTokenType.Float => JsonValueType.Number,
JTokenType.String or JTokenType.Date or JTokenType.Raw or JTokenType.Bytes or JTokenType.Guid or JTokenType.Uri or JTokenType.TimeSpan => JsonValueType.String,
JTokenType.Boolean when Token.Value<bool>() => JsonValueType.True,
JTokenType.Boolean when !Token.Value<bool>() => JsonValueType.False,
_ => JsonValueType.Null
};

public DefaultJsonNode(JToken token, JsonSerializer serializer)
{
Expand All @@ -37,34 +56,20 @@ public DefaultJsonNode(JToken token, JsonSerializer serializer)
/// <returns>
/// The string representing this node.
/// </returns>
public string ToString(Formatting formatting)
public string ToJsonString(JsonFormatting formatting)
{
return Token.ToString(formatting);
}

/// <summary>
/// Formats this node into an indented JSON representation.
/// </summary>
/// <returns>
/// The string representing this node.
/// </returns>
public override string ToString()
{
return Token.ToString(Formatting.Indented);
return Token.ToString(formatting switch
{
JsonFormatting.Indented => Formatting.Indented,
_ => Formatting.None
});
}

/// <summary>
/// Creates a new <see cref="DefaultJsonNode"/> from the specified object.
/// </summary>
/// <param name="obj"> The object to create the node for. </param>
/// <param name="serializer"> The default JSON serializer. </param>
/// <returns>
/// A JSON node representing the object.
/// </returns>
public static IJsonNode? Create(object? obj, DefaultJsonSerializer serializer)
[return: NotNullIfNotNull("obj")]
internal static IJsonNode? Create(object? obj, JsonSerializer serializer)
{
var token = obj != null ? JToken.FromObject(obj) : JValue.CreateNull();
return Create(token, serializer.UnderlyingSerializer);
var token = obj != null ? JToken.FromObject(obj, serializer) : JValue.CreateNull();
return Create(token, serializer);
}

[return: NotNullIfNotNull("token")]
Expand All @@ -79,4 +84,12 @@ public override string ToString()
_ => throw new InvalidOperationException("Unknown JSON token type.")
};
}
}

[return: NotNullIfNotNull("node")]
internal static JToken? GetJToken(IJsonNode? node)
{
return node != null
? Guard.IsAssignableToType<DefaultJsonNode>(node).Token
: null;
}
}
Loading
Loading