beeradmoore/dlss-swapper · error · Exception

Could not deserialize GOGEmbedFilteredResponse for url

Error message

Could not deserialize GOGEmbedFilteredResponse for url, {url}

What it means

UpdateCacheImageAsync also fetches a GOG 'embed filtered' products endpoint and deserializes it into GOGEmbedFilteredResponse; a null result throws this error. Null indicates an empty body or JSON that does not match the GOGEmbedFilteredResponse contract, blocking resolution of embed product data for the cache image.

Solutions

  1. Curl the embed-filtered URL and confirm it returns the expected JSON shape
  2. Retry after transient GOG failures or rate limiting
  3. Regenerate/update the deserialization contract if GOG changed the response schema
  4. Log the raw response body when deserialization fails to spot HTML/error pages

Example fix

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

Strategy: try-catch

Validate before calling

// preflight the embed-filtered URL
using var probe = await httpClient.GetAsync(url);
var body = await probe.Content.ReadAsStringAsync();
if (body.Contains("<html", StringComparison.OrdinalIgnoreCase))
    throw new InvalidOperationException($"GOG embed endpoint returned HTML for {url} (bot protection or error page).");

Type guard

static bool LooksLikeEmbedFiltered(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 GOGEmbedFilteredResponse"))
{
    logger.LogWarning(ex, "GOG embed response unusable for {Url}; skipping cache image.", url);
}

Prevention

When it happens

Trigger: Calling UpdateCacheImageAsync when the embed-filtered endpoint returns an empty body, HTML error/consent page, or JSON incompatible with the source-generated GOGEmbedFilteredResponse deserializer.

Common situations: GOG endpoint shape changed after an API update; bot-protection/Cloudflare page returned instead of JSON; wrong game URL producing 404; transient GOG outage or rate limiting.

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/8b9142d35f85080d. Report an issue: GitHub.

Appendix: source

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

        }


        // If catalog failed fall back to embeded search.
        /*
        try
        {
            var url = "https://embed.gog.com/games/ajax/filtered?mediaType=game&search=" + Uri.EscapeDataString(Title);

            var fileDownloader = new FileDownloader(url);
            using (var memoryStream = new MemoryStream())
            {
                await fileDownloader.DownloadFileToStreamAsync(memoryStream);
                memoryStream.Position = 0;

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

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

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

                    if (string.IsNullOrWhiteSpace(product.BoxImage) == false)
                    {
                        await DownloadCoverAsync($"https:{product.BoxImage}_glx_vertical_cover.webp").ConfigureAwait(false);
                        return;

View on GitHub (pinned to ab9b1e2d4b)