beeradmoore/dlss-swapper · error · Exception

Could not deserialize GOGCatalogResponse for url

Error message

Could not deserialize GOGCatalogResponse for url, {url}

What it means

GOGGame.UpdateCacheImageAsync downloads a GOG catalog lookup for the game's URL and deserializes it into GOGCatalogResponse with System.Text.Json; a null result throws this error. Null means the response body was empty or not JSON matching the GOGCatalogResponse contract, so the cache image update cannot proceed.

Solutions

  1. Verify the catalog URL for this game returns valid JSON (curl and inspect)
  2. Check GOG API status / retry later on transient failures
  3. Confirm PlatformId and the constructed URL are correct for the game
  4. Wrap deserialization to catch JsonException and log the raw body for diagnosis

Example fix

// before
var catalogResponse = JsonSerializer.Deserialize(memoryStream, SourceGenerationContext.Default.GOGCatalogResponse);
if (catalogResponse is null)
{
    throw new Exception($"Could not deserialize GOGCatalogResponse for url, {url}");
}
// after
catalogResponse = JsonSerializer.Deserialize(memoryStream, SourceGenerationContext.Default.GOGCatalogResponse);
if (catalogResponse is null)
{
    throw new Exception($"Could not deserialize GOGCatalogResponse for url, {url}. Body length: {memoryStream.Length}");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight the catalog URL
using var probe = await httpClient.GetAsync(url);
var body = await probe.Content.ReadAsStringAsync();
if (!body.TrimStart().StartsWith("{"))
    throw new InvalidOperationException($"GOG catalog returned non-JSON for {url}.");

Type guard

static bool LooksLikeGogCatalog(string json) => json.TrimStart().StartsWith("{") && json.Contains("products", StringComparison.OrdinalIgnoreCase);

Try / catch

try
{
    await gogGame.UpdateCacheImageAsync();
}
catch (Exception ex) when (ex.Message.Contains("Could not deserialize GOGCatalogResponse"))
{
    logger.LogWarning(ex, "GOG catalog unavailable for {Url}; skipping cache image.", url);
}

Prevention

When it happens

Trigger: Calling UpdateCacheImageAsync when the GOG catalog endpoint returns an empty body, an HTML error page, or JSON whose shape doesn't satisfy the source-generated GOGCatalogResponse deserializer (leading to null/empty stream).

Common situations: GOG API changes or downtime; wrong/removed game URL or platform id; region-locked or 403 page returned instead of JSON; connectivity issues yielding an empty stream.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of beeradmoore/dlss-swapper@ab9b1e2d4b (2026-09-15). Data as JSON: /api/errors/996baaef1894d302. Report an issue: GitHub.

Appendix: source

Thrown at src/Data/GOG/GOGGame.cs:78

        // Some games are not found here (eg. Warecraft III) and instead will fallback
        // to using the direct product loading below. Unfortuantly using the product
        // endpoint does not contain boxart image urls so the images are not really
        // what we want, but at least there is images.


        try
        {
            var url = "https://catalog.gog.com/v1/catalog?order=desc:score&productType=in:game&query=like:" + Uri.EscapeDataString(Title);
            var fileDownloader = new FileDownloader(url);
            using (var memoryStream = new MemoryStream())
            {
                await fileDownloader.DownloadFileToStreamAsync(memoryStream);
                memoryStream.Position = 0;

                var catalogResponse = JsonSerializer.Deserialize(memoryStream, SourceGenerationContext.Default.GOGCatalogResponse);
                if (catalogResponse is null)
                {
                    throw new Exception($"Could not deserialize GOGCatalogResponse for url, {url}");
                }

                if (catalogResponse.Products.Length == 0)
                {
                    throw new Exception($"Could not find any GOGCatalogProduct for url, {url}");
                }

                foreach (var product in catalogResponse.Products)
                {
                    if (product.Id.Equals(PlatformId, StringComparison.OrdinalIgnoreCase) == false)
                    {
                        continue;
                    }

                    if (string.IsNullOrWhiteSpace(product.CoverVertical) == false)
                    {
                        await DownloadCoverAsync(product.CoverVertical).ConfigureAwait(false);
                        return;

View on GitHub (pinned to ab9b1e2d4b)