diff --git a/MetaBrainz.MusicBrainz/Objects/Searches/FoundReleases.cs b/MetaBrainz.MusicBrainz/Objects/Searches/FoundReleases.cs index 6133dca..05d6e09 100644 --- a/MetaBrainz.MusicBrainz/Objects/Searches/FoundReleases.cs +++ b/MetaBrainz.MusicBrainz/Objects/Searches/FoundReleases.cs @@ -1,7 +1,12 @@ using System; using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using MetaBrainz.Common.Json; +using MetaBrainz.MusicBrainz.Interfaces.Entities; using MetaBrainz.MusicBrainz.Interfaces.Searches; +using MetaBrainz.MusicBrainz.Objects.Entities; namespace MetaBrainz.MusicBrainz.Objects.Searches { @@ -13,6 +18,178 @@ public FoundReleases(Query query, string queryString, int? limit = null, int? of public override IReadOnlyList Results => this.CurrentResult?.Releases ?? Array.Empty(); + /// + /// Custom converter to work around SEARCH-579.
+ /// This fixes Issue #1 by checking whether the + /// 'packaging' property is a string or an object, and handling it accordingly. + ///
+ public sealed class WorkaroundForSearchBug579 : JsonConverter { + + private sealed class ObjectConverter : JsonConverter { + + public override Release Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException($"Expected the start of a Release object, but got a {reader.TokenType} instead (at offset {reader.TokenStartIndex})."); + var r = new Release(); + var complete = false; + while (reader.Read()) { + if (reader.TokenType == JsonTokenType.EndObject) { + complete = true; + break; + } + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException($"Expected a property of a Release object, but got a {reader.TokenType} instead (at offset {reader.TokenStartIndex})."); + var property = reader.GetRawStringValue(); + if (!reader.Read()) + throw new JsonException($"Expected value for the '{property}' property of Release object not encountered."); + switch (property) { + case "aliases": + r.Aliases = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "annotation": + r.Annotation = reader.GetString(); + break; + case "artist-credit": + r.ArtistCredit = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "asin": + r.Asin = reader.GetString(); + break; + case "barcode": + r.BarCode = reader.GetString(); + break; + case "collections": + r.Collections = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "country": + r.Country = reader.GetString(); + break; + case "cover-art-archive": + r.CoverArtArchive = JsonSerializer.Deserialize(ref reader, options); + break; + case "date": + r.Date = JsonSerializer.Deserialize(ref reader, options); + break; + case "disambiguation": + r.Disambiguation = reader.GetString(); + break; + case "genres": + r.Genres = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "id": + r.MbId = reader.GetGuid(); + break; + case "label-info": + r.LabelInfo = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "media": + r.Media = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "packaging": + if (reader.TokenType == JsonTokenType.String) // packaging: "foo" + r.Packaging = reader.GetRawStringValue(); + else if (reader.TokenType == JsonTokenType.StartObject) { // packaging: { "name": "foo", id: "guid" } + while (reader.Read()) { + if (reader.TokenType == JsonTokenType.EndObject) + break; + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException($"Expected a property of a Release object's 'packaging' field, but got a {reader.TokenType} instead (at offset {reader.TokenStartIndex})."); + var subprop = reader.GetRawStringValue(); + if (!reader.Read()) + throw new JsonException($"Expected value for the '{subprop}' property of Release object's 'packaging' field not encountered."); + if (subprop == "name") + r.Packaging = reader.GetString(); + else if (subprop == "id") + r.PackagingId = reader.GetGuid(); + else { + r.UnhandledProperties ??= new Dictionary(); + r.UnhandledProperties.Add(property + ":" + subprop, JsonSerializer.Deserialize(ref reader, typeof(object), options)); + } + } + } + else if (reader.TokenType != JsonTokenType.Null) + throw new JsonException($"Unsupported value (of type {reader.TokenType}) found for the 'packaging' property of a Release object at offset {reader.TokenStartIndex}."); + break; + case "packaging-id": + r.PackagingId = reader.GetGuid(); + break; + case "quality": + r.Quality = reader.GetString(); + break; + case "relations": + r.Relationships = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "release-events": + r.ReleaseEvents = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "release-group": + r.ReleaseGroup = JsonSerializer.Deserialize(ref reader, options); + break; + case "score": + r.SearchScore = reader.GetByte(); + break; + case "status": + r.Status = reader.GetString(); + break; + case "status-id": + r.StatusId = reader.GetGuid(); + break; + case "tags": + r.Tags = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "text-representation": + r.TextRepresentation = JsonSerializer.Deserialize(ref reader, options); + break; + case "title": + r.Title = reader.GetString(); + break; + case "user-genres": + r.UserGenres = JsonSerializer.Deserialize?>(ref reader, options); + break; + case "user-tags": + r.UserTags = JsonSerializer.Deserialize?>(ref reader, options); + break; + default: + r.UnhandledProperties ??= new Dictionary(); + r.UnhandledProperties.Add(property, JsonSerializer.Deserialize(ref reader, typeof(object), options)); + break; + } + } + if (!complete) + throw new JsonException("Expected end of Release object not encountered."); + return r; + } + + public override void Write(Utf8JsonWriter writer, Release value, JsonSerializerOptions options) { + throw new NotSupportedException("This converter only handles deserialization."); + } + + } + + public override Release[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException($"Expected the start of a Release array, but got a {reader.TokenType} instead (at offset {reader.TokenStartIndex})."); + var releases = new List(); + var complete = false; + var oc = new ObjectConverter(); + while (reader.Read()) { + if (reader.TokenType == JsonTokenType.EndArray) { + complete = true; + break; + } + releases.Add(oc.Read(ref reader, typeof(Release), options)); + } + if (!complete) + throw new JsonException("Expected end of Release array not encountered."); + return releases.ToArray(); + } + + public override void Write(Utf8JsonWriter writer, Release[] value, JsonSerializerOptions options) { + throw new NotSupportedException("This converter only handles deserialization."); + } + + } + } } diff --git a/MetaBrainz.MusicBrainz/Objects/Searches/SearchResults.cs b/MetaBrainz.MusicBrainz/Objects/Searches/SearchResults.cs index 946f6b2..77f492c 100644 --- a/MetaBrainz.MusicBrainz/Objects/Searches/SearchResults.cs +++ b/MetaBrainz.MusicBrainz/Objects/Searches/SearchResults.cs @@ -86,6 +86,7 @@ internal sealed class JSON : JsonBasedObject { public ReleaseGroup[]? ReleaseGroups { get; set; } [JsonPropertyName("releases")] + [JsonConverter(typeof(FoundReleases.WorkaroundForSearchBug579))] public Release[]? Releases { get; set; } [JsonPropertyName("series")]