This commit is contained in:
2026-07-19 12:13:14 +02:00
commit 6337e63a77
46 changed files with 4411 additions and 0 deletions
@@ -0,0 +1,139 @@
namespace FinlyticAssets.Models.DataToObject.TradeRepublic;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
// 1. Der Response-Wrapper
public record TradeRepublicAssetResponse(
[property: JsonPropertyName("correlationId")] string CorrelationId,
[property: JsonPropertyName("resultCount")] int ResultCount,
[property: JsonPropertyName("results")] IList<TradeRepublicAsset> Results
);
// 2. Das Tag-Objekt
public record TradeRepublicTag
{
[JsonPropertyName("id")] public string Id { get; init; } = "";
[JsonPropertyName("name")] public string Name { get; init; } = "";
[JsonPropertyName("type")] public string Type { get; init; } = "";
}
// 3. Die Basisklasse MIT UNSEREM CUSTOM CONVERTER (Kein [JsonPolymorphic] mehr!)
[JsonConverter(typeof(TradeRepublicAssetConverter))]
public record TradeRepublicAsset
{
[JsonPropertyName("isin")] public string Isin { get; init; } = "";
[JsonPropertyName("name")] public string Name { get; init; } = "";
[JsonPropertyName("type")] public string Type { get; init; } = "";
[JsonPropertyName("instrumentCategory")] public string InstrumentCategory { get; init; } = "";
[JsonPropertyName("hasCfd")] public bool HasCfd { get; init; }
[JsonPropertyName("imageId")] public string? ImageId { get; init; }
[JsonPropertyName("tags")]
public IReadOnlyList<TradeRepublicTag> Tags { get; init; } = Array.Empty<TradeRepublicTag>();
}
// 4. Die spezifischen Klassen (inklusive Bond und Derivative aus deinem JSON!)
public record TradeRepublicStock : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
}
public record TradeRepublicCrypto : TradeRepublicAsset
{
[JsonPropertyName("subtitle")] public string Subtitle { get; init; } = "";
[JsonPropertyName("searchSubtitle")] public string SearchSubtitle { get; init; } = "";
}
public record TradeRepublicEtf : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
[JsonPropertyName("etfDescription")] public string EtfDescription { get; init; } = "";
[JsonPropertyName("mappedEtfIndexName")] public string MappedEtfIndexName { get; init; } = "";
[JsonPropertyName("subtitle")] public string Subtitle { get; init; } = "";
[JsonPropertyName("searchSubtitle")] public string SearchSubtitle { get; init; } = "";
}
public record TradeRepublicSynthetic : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
}
// NEU: Anleihen
public record TradeRepublicBond : TradeRepublicAsset
{
[JsonPropertyName("bondIssuerName")] public string BondIssuerName { get; init; } = "";
[JsonPropertyName("searchSubtitle")] public string SearchSubtitle { get; init; } = "";
}
// NEU: Derivate (Hebeleffekte etc.)
public record TradeRepublicDerivative : TradeRepublicAsset
{
[JsonPropertyName("derivativeProductCategories")]
public IReadOnlyList<string> DerivativeProductCategories { get; init; } = Array.Empty<string>();
[JsonIgnore]
public string? UnderlyingIsin
{
get
{
// Wenn die ImageId z.B. "logos/US0378331005/v2" ist...
if (!string.IsNullOrEmpty(ImageId) && ImageId.StartsWith("logos/"))
{
var parts = ImageId.Split('/');
if (parts.Length >= 2)
{
return parts[1]; // Gibt "US0378331005" zurück
}
}
return null; // Falls das Format mal anders ist
}
}
}
// 5. Der Custom Converter - Die Maschine, die das JSON scannt und verteilt
public class TradeRepublicAssetConverter : JsonConverter<TradeRepublicAsset>
{
public override TradeRepublicAsset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var doc = JsonDocument.ParseValue(ref reader);
var root = doc.RootElement;
// Wir scannen nach instrumentType, egal wo im JSON es steht!
string? instrumentType = null;
if (root.TryGetProperty("instrumentType", out var typeElement))
{
instrumentType = typeElement.GetString();
}
// Wir werfen das JSON gezielt in die richtige Klasse
TradeRepublicAsset? result = instrumentType switch
{
"stock" => JsonSerializer.Deserialize<TradeRepublicStock>(root.GetRawText(), options),
"crypto" => JsonSerializer.Deserialize<TradeRepublicCrypto>(root.GetRawText(), options),
"fund" => JsonSerializer.Deserialize<TradeRepublicEtf>(root.GetRawText(), options),
"synthetic" => JsonSerializer.Deserialize<TradeRepublicSynthetic>(root.GetRawText(), options),
"bond" => JsonSerializer.Deserialize<TradeRepublicBond>(root.GetRawText(), options),
"derivative" => JsonSerializer.Deserialize<TradeRepublicDerivative>(root.GetRawText(), options),
// Wenn TR einen Typ schickt, den wir noch nicht kennen: Fallback nutzen!
_ => JsonSerializer.Deserialize<TradeRepublicAssetFallback>(root.GetRawText(), options)
};
return result ?? new TradeRepublicAssetFallback();
}
public override void Write(Utf8JsonWriter writer, TradeRepublicAsset value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
// Ein reiner Fallback-Record, der nur intern vom Converter genutzt wird
file record TradeRepublicAssetFallback : TradeRepublicAsset;