elsa-workflows/elsa-core · error · JsonException

Failed to parse JsonDocument

Error message

Failed to parse JsonDocument

What it means

Thrown in FlowchartJsonConverter.Read when the JSON token for a Flowchart activity cannot be parsed into a JsonDocument. This guards against malformed JSON before reading id, nodeId, name, type, version, and connections properties during workflow definition deserialization.

Solutions

  1. Parse and validate the definition JSON independently (JsonDocument.Parse) to pinpoint the malformed fragment.
  2. Check storage/transport layers for truncation or encoding issues (column length limits, charset mismatches).
  3. Ensure the flowchart is serialized as a JSON object, not wrapped in a string.
  4. Catch JsonException around deserialization and include the failing JSON snippet in diagnostics.

Example fix

// before
var flowchart = JsonSerializer.Deserialize<Activities.Flowchart>(json); // throws on malformed json
// after
using var doc = JsonDocument.Parse(json); // validate first
var flowchart = json.Deserialize<Activities.Flowchart>();
Defensive patterns

Strategy: try-catch

Validate before calling

using var doc = JsonDocument.Parse(definitionJson); // pre-validate whole definition

Type guard

static bool IsJsonObjectToken(string json) { try { using var d = JsonDocument.Parse(json); return d.RootElement.ValueKind == JsonValueKind.Object; } catch (JsonException) { return false; } }

Try / catch

try { var flowchart = JsonSerializer.Deserialize<Activities.Flowchart>(json); }
catch (JsonException ex)
{
    logger.LogError(ex, "Malformed flowchart JSON: {Message}", ex.Message);
    throw new WorkflowDefinitionFormatException("Definition contains invalid flowchart JSON", ex);
}

Prevention

When it happens

Trigger: Deserializing a workflow definition where the flowchart node's JSON is syntactically invalid — truncated JSON, wrong token type (string/number instead of object), or encoding corruption.

Common situations: Corrupted stored workflow definitions; truncated HTTP request bodies; hand-edited JSON with syntax errors; systems that re-encode the payload incorrectly during round-trips.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/665e4b9092c13106. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.Workflows.Core/Activities/Flowchart/Serialization/FlowchartJsonConverter.cs:27

using Elsa.Common.Serialization;

namespace Elsa.Workflows.Activities.Flowchart.Serialization;

/// <summary>
/// A JSON converter for <see cref="Activities.Flowchart"/>.
/// </summary>
[UsedImplicitly]
public class FlowchartJsonConverter(IIdentityGenerator identityGenerator, ISerializationTypeRegistry workflowJsonTypeRegistry, ILoggerFactory loggerFactory) : JsonConverter<Activities.Flowchart>
{
    private const string AllActivitiesKey = "allActivities";
    private const string AllConnectionsKey = "allConnections";
    private const string NotFoundConnectionsKey = "notFoundConnections";

    /// <inheritdoc />
    public override Activities.Flowchart Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (!JsonDocument.TryParseValue(ref reader, out var doc))
            throw new JsonException("Failed to parse JsonDocument");

        var id = doc.RootElement.TryGetProperty("id", out var idAttribute) ? idAttribute.GetString()! : identityGenerator.GenerateId();
        var nodeId = doc.RootElement.TryGetProperty("nodeId", out var nodeIdAttribute) ? nodeIdAttribute.GetString() : null;
        var name = doc.RootElement.TryGetProperty("name", out var nameElement) ? nameElement.GetString() : null;
        var type = doc.RootElement.TryGetProperty("type", out var typeElement) ? typeElement.GetString() : null;
        var version = doc.RootElement.TryGetProperty("version", out var versionElement) ? versionElement.GetInt32() : 1;
        var runAsynchronously = doc.RootElement.TryGetProperty("runAsynchronously", out var runAsyncElement) && runAsyncElement.GetBoolean();

        var connectionsElement = doc.RootElement.TryGetProperty("connections", out var connectionsEl) ? connectionsEl : default;
        var activitiesElement = doc.RootElement.TryGetProperty("activities", out var activitiesEl) ? activitiesEl : default;
        var activities = activitiesElement.ValueKind != JsonValueKind.Undefined ? activitiesElement.Deserialize<ICollection<IActivity>>(options) ?? new List<IActivity>() : new List<IActivity>();
        var activityDictionary = activities.ToDictionary(x => x.Id);
        var connections = DeserializeConnections(connectionsElement, activityDictionary, options);
        var notFoundConnections = GetNotFoundConnections(doc.RootElement, activityDictionary, connections, options);
        var connectionsToRestore = FindConnectionsThatCanBeRestored(notFoundConnections, activities);
        var connectionComparer = new ConnectionComparer();
        var connectionsWithRestoredOnes = connections.Except(notFoundConnections, connectionComparer).Union(connectionsToRestore, connectionComparer).ToList();

View on GitHub (pinned to fe9217bdfa)