beeradmoore/dlss-swapper · error · Exception

Could not deserialize manifest.json.

Error message

Could not deserialize manifest.json.

What it means

Thrown by DLLManager.UpdateManifestAsync when System.Text.Json deserialization of the downloaded manifest.json stream returns null. This indicates the stream did not contain a valid JSON object matching the Manifest schema (DeserializeAsync returns null only for empty input, so an empty or zero-length stream is the typical cause). The library treats a manifest it cannot parse as unrecoverable and stops updating.

Solutions

  1. Check the downloaded stream length before deserializing and fail earlier with a clearer download error
  2. Verify the manifest URL returns a valid manifest.json (curl it and inspect the body)
  3. Re-run the update after confirming network/proxy connectivity
  4. Pin to a known-good manifest source or bundled fallback manifest

Example fix

// before
var manifest = await JsonSerializer.DeserializeAsync(memoryStream, SourceGenerationContext.Default.Manifest);
if (manifest is null)
{
    throw new Exception("Could not deserialize manifest.json.");
}
// after
if (memoryStream.Length == 0)
{
    throw new Exception($"Manifest download was empty ({manifestUrl}).");
}
var manifest = await JsonSerializer.DeserializeAsync(memoryStream, SourceGenerationContext.Default.Manifest);
if (manifest is null)
{
    throw new Exception($"Could not deserialize manifest.json from {manifestUrl} (empty body).");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling UpdateManifestAsync
using var head = await httpClient.GetAsync(manifestUrl, HttpCompletionOption.ResponseHeadersRead);
if (!head.IsSuccessStatusCode || head.Content.Headers.ContentLength == 0)
    throw new InvalidOperationException($"Manifest at {manifestUrl} is empty or unreachable.");

Type guard

static bool HasManifestContent(Stream s) => s.CanSeek && s.Length > 0;

Try / catch

try
{
    await dllManager.UpdateManifestAsync();
}
catch (Exception ex) when (ex.Message.Contains("Could not deserialize manifest.json"))
{
    logger.LogWarning(ex, "Manifest download/parse failed; keeping existing manifest.");
}

Prevention

When it happens

Trigger: Calling UpdateManifestAsync when the remote manifest download produced an empty stream (failed/redirected download writing 0 bytes), or the response body is whitespace/empty rather than Manifest-shaped JSON.

Common situations: CDN or proxy returning an empty 200 response; manifest URL changed and now serves an empty body; network middlebox stripping the body; offline/captive portal returning an empty stub page.

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/431763dfd5a1f7ce. Report an issue: GitHub.

Appendix: source

Thrown at src/Data/DLLManager.cs:173

                var fileDownloader = new FileDownloader("https://beeradmoore.github.io/dlss-swapper/manifest.json", 0);
                await fileDownloader.DownloadFileToStreamAsync(memoryStream);

                memoryStream.Position = 0;

                var newManifestHash = memoryStream.GetMD5Hash();

                // If the old manifest on disk is the same as the new one there is no need to do anything as it will already be loaded.
                if (oldManifestHash == newManifestHash)
                {
                    return true;
                }

                memoryStream.Position = 0;

                var manifest = await JsonSerializer.DeserializeAsync(memoryStream, SourceGenerationContext.Default.Manifest);
                if (manifest is null)
                {
                    throw new Exception("Could not deserialize manifest.json.");
                }

                Manifest = manifest;

                try
                {
                    Storage.CreateDirectoryForFileIfNotExists(manifestPath);
                    using (var stream = File.Create(manifestPath))
                    {
                        memoryStream.Position = 0;
                        memoryStream.CopyTo(stream);
                    }
                }
                catch (Exception err)
                {
                    Logger.Error(err);
                    Debugger.Break();
                }

View on GitHub (pinned to ab9b1e2d4b)