Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
57 changes: 57 additions & 0 deletions src/Streamlabs.SocketClient/Converters/FlexibleObjectConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Streamlabs.SocketClient.Converters;

/// <summary>
/// Provides a flexible JSON converter for <typeparamref name="T"/> that handles mixed input types.
/// This converter can deserialize <typeparamref name="T"/> from a standard JSON object,
/// an escaped JSON string (double-encoded), or gracefully handle plain non-JSON strings by returning null.
/// </summary>
/// <typeparam name="T">The reference type to deserialize into.</typeparam>
public class FlexibleObjectConverter<T> : JsonConverter<T>
where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.StartObject or JsonTokenType.StartArray:
return JsonSerializer.Deserialize<T>(ref reader, options);
case JsonTokenType.String:
{
string? value = reader.GetString();
if (value == null)
{
return null;
}

string trimmed = value.Trim();

bool isJsonObject = trimmed.Length > 0 && trimmed[0] == '{' && trimmed[^1] == '}';
bool isJsonArray = trimmed.Length > 0 && trimmed[0] == '[' && trimmed[^1] == ']';

if (isJsonObject || isJsonArray)
{
try
{
return JsonSerializer.Deserialize<T>(trimmed, options);
}
catch (JsonException)
{
return null;
}
}

break;
}
}

return null;
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, options);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public sealed record StreamlabelsMessageData : IHasMessageId, IHasPriority
public required string SessionMostRecentMonthlyDonator { get; init; }

[JsonPropertyName("cloudbot_counter_deaths")]
public required string CloudbotCounterDeaths { get; init; }
public string? CloudbotCounterDeaths { get; init; } // this property might not be present

[JsonPropertyName("monthly_subscriber_count")]
[JsonConverter(typeof(IntStringConverter))]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ namespace Streamlabs.SocketClient.Messages.DataTypes;
public sealed record StreamlabelsUnderlyingMessageData : IHasMessageId, IHasPriority
{
[JsonPropertyName("donation_goal")]
public required string DonationGoal { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<StreamlabelsUnderlyingMessageDonationGoal>))]
public StreamlabelsUnderlyingMessageDonationGoal? DonationGoal { get; init; }

[JsonPropertyName("most_recent_donator")]
public required Donator MostRecentDonator { get; init; }

[JsonPropertyName("session_most_recent_donator")]
public required string SessionMostRecentDonator { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<Donator>))]
public Donator? SessionMostRecentDonator { get; init; }

[JsonPropertyName("session_donators")]
public required string SessionDonators { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<IReadOnlyCollection<Donator>>))]
public IReadOnlyCollection<Donator>? SessionDonators { get; init; }

[JsonPropertyName("total_donation_amount")]
public required DonationAmount TotalDonationAmount { get; init; }
Expand All @@ -37,31 +40,40 @@ public sealed record StreamlabelsUnderlyingMessageData : IHasMessageId, IHasPrio
public required TopDonator AllTimeTopDonator { get; init; }

[JsonPropertyName("monthly_top_donator")]
public required string MonthlyTopDonator { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<TopDonator>))]
public TopDonator? MonthlyTopDonator { get; init; }

[JsonPropertyName("weekly_top_donator")]
public required string WeeklyTopDonator { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<TopDonator>))]
public TopDonator? WeeklyTopDonator { get; init; }

[JsonPropertyName("30day_top_donator")]
public required string ThirtyDayTopDonator { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<TopDonator>))]
public TopDonator? ThirtyDayTopDonator { get; init; }

[JsonPropertyName("session_top_donator")]
public required string SessionTopDonator { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<TopDonator>))]
public TopDonator? SessionTopDonator { get; init; }

[JsonPropertyName("all_time_top_donators")]
public required IReadOnlyCollection<TopDonator> AllTimeTopDonators { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<IReadOnlyCollection<TopDonator>>))]
public IReadOnlyCollection<TopDonator>? AllTimeTopDonators { get; init; }

[JsonPropertyName("monthly_top_donators")]
public required string MonthlyTopDonators { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<IReadOnlyCollection<TopDonator>>))]
public IReadOnlyCollection<TopDonator>? MonthlyTopDonators { get; init; }

[JsonPropertyName("weekly_top_donators")]
public required string WeeklyTopDonators { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<IReadOnlyCollection<TopDonator>>))]
public IReadOnlyCollection<TopDonator>? WeeklyTopDonators { get; init; }

[JsonPropertyName("30day_top_donators")]
public required string ThirtyDayTopDonators { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<IReadOnlyCollection<TopDonator>>))]
public IReadOnlyCollection<TopDonator>? ThirtyDayTopDonators { get; init; }

[JsonPropertyName("session_top_donators")]
public required string SessionTopDonators { get; init; }
[JsonConverter(typeof(FlexibleObjectConverter<IReadOnlyCollection<TopDonator>>))]
public IReadOnlyCollection<TopDonator>? SessionTopDonators { get; init; }

[JsonPropertyName("all_time_top_donations")]
public required IReadOnlyCollection<TopDonationAmount> AllTimeTopDonations { get; init; }
Expand Down Expand Up @@ -133,7 +145,7 @@ public sealed record StreamlabelsUnderlyingMessageData : IHasMessageId, IHasPrio
public required string SessionMostRecentMonthlyDonator { get; init; }

[JsonPropertyName("cloudbot_counter_deaths")]
public required Counter CloudbotCounterDeaths { get; init; }
public Counter? CloudbotCounterDeaths { get; init; }

[JsonPropertyName("monthly_subscriber_count")]
public required Count MonthlySubscriberCount { get; init; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Text.Json.Serialization;

namespace Streamlabs.SocketClient.Messages.DataTypes;

public sealed record StreamlabelsUnderlyingMessageDonationGoal {
[JsonPropertyName("title")]
public required string Title { get; init; }

[JsonPropertyName("currentAmount")]
public required string CurrentAmount { get; init; }

[JsonPropertyName("goalAmount")]
public required string GoalAmount { get; init; }
}
Loading