elsa-workflows/elsa-core · error · JsonException
Expected StartObject token
Error message
Expected StartObject token
What it means
HttpHeadersConverter is a System.Text.Json JsonConverter for the HttpHeaders type, which models headers as name-to-array-of-values. Its Read method only accepts a JSON object at the top level, so any other leading token raises JsonException('Expected StartObject token').
Solutions
- Ensure the JSON value for HttpHeaders is an object like {"Accept":["text/plain"]}, not an array or string
- Fix the source that produced the malformed JSON (e.g. double-serializing headers into a string)
- Add a try/catch around JsonSerializer.Deserialize<HttpHeaders> and reject/log the payload
- Pre-validate with JsonDocument that the token at the headers position is JsonValueKind.Object
Example fix
// before
var headers = JsonSerializer.Deserialize<HttpHeaders>("[{\"Accept\":[\"text/plain\"]}]");
// after
var headers = JsonSerializer.Deserialize<HttpHeaders>("{\"Accept\":[\"text/plain\"]}"); Defensive patterns
Strategy: type-guard
Validate before calling
using var doc = JsonDocument.Parse(json);
if (doc.RootElement.ValueKind != JsonValueKind.Object)
throw new FormatException("Headers payload must be a JSON object"); Type guard
bool IsHttpHeadersPayload(JsonElement e) => e.ValueKind == JsonValueKind.Object && e.EnumerateObject().All(p => p.Value.ValueKind is JsonValueKind.String or JsonValueKind.Array);
Try / catch
try { headers = JsonSerializer.Deserialize<HttpHeaders>(json, options); }
catch (JsonException ex)
{
logger.LogWarning(ex, "Malformed HttpHeaders JSON");
headers = new HttpHeaders();
} Prevention
- Always serialize headers through the HttpHeaders type, never hand-build the JSON
- Validate payload shape with JsonDocument before deserializing untrusted input
- Pin one serializer configuration so headers are written consistently across versions
When it happens
Trigger: Deserializing a property typed as HttpHeaders from JSON that is an array, string, number, or null instead of an object — e.g. persisted workflow state or an API payload where headers were stored as "[{...}]" or as a raw string.
Common situations: Hand-edited or migrated workflow instance state; older/other serializer versions that wrote headers differently; sending headers JSON built with an array wrapper by client code.
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
- Expected a PropertyName token
- Expected a String or StartArray token
- The serialization type alias is missing.
- Unsupported console stream value
- The persisted external authentication value could not be…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/408aaab8988eea59.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Http/Serialization/HttpHeadersConverter.cs:15
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Elsa.Http.Serialization;
/// <summary>
/// A custom JSON converter for HttpHeaders that supports both single and multiple values.
/// </summary>
public class HttpHeadersConverter : JsonConverter<HttpHeaders>
{
/// <inheritdoc />
public override HttpHeaders Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException("Expected StartObject token");
var headers = new HttpHeaders();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
return headers;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException("Expected a PropertyName token");
var key = reader.GetString()!;
reader.Read();
// If the next token is not a StartArray token, then we expect a String token.
switch (reader.TokenType)
{
case JsonTokenType.StartArray:View on GitHub (pinned to fe9217bdfa)