elsa-workflows/elsa-core · error · JsonException

Expected a string metadata value.

Error message

Expected a string metadata value.

What it means

This JsonException is thrown by OrdinalIgnoreCaseDictionaryConverter.Read when deserializing a secret metadata dictionary and a property's value token is not a JSON string (and not null). The converter only accepts string values (or null, which is skipped), so any number, boolean, array, or nested object in the metadata object is rejected.

Solutions

  1. Convert all metadata values to JSON strings in the source payload (e.g. 30 -> "30", true -> "true").
  2. Fix the producer/client so metadata values are serialized as strings before persisting or sending them.
  3. If non-string values are required, change the property type to Dictionary<string, string> with a permissive converter or Dictionary<string, JsonElement> instead of using this converter.
  4. If migrating old data, run a one-time migration that stringifies metadata values.

Example fix

// before
var secret = JsonSerializer.Deserialize<Secret>(json); // metadata: {"ttl": 30}
// after
// fix the JSON: {"ttl": "30"}
var secret = JsonSerializer.Deserialize<Secret>(json);
Defensive patterns

Strategy: validation

Validate before calling

// Validate all metadata values are strings before serializing/deserializing
bool MetadataIsStringOnly(IDictionary<string, string> metadata) =>
    metadata.Values.All(v => v is string);

Type guard

bool IsStringMetadata(JsonElement prop) => prop.ValueKind == JsonValueKind.String;

Try / catch

try { var secret = JsonSerializer.Deserialize<Secret>(json); }
catch (JsonException e) when (e.Message.Contains("Expected a string metadata value")) { /* repair metadata JSON or reject payload */ }

Prevention

When it happens

Trigger: Calling JsonSerializer.Deserialize on a payload containing a secret metadata dictionary (e.g. Secret.Metadata) where one of the properties is a non-string JSON value such as {"ttl": 30} or {"enabled": true} instead of {"ttl": "30"}.

Common situations: Hand-edited or migrated secret JSON files, API clients that send numeric/boolean metadata, or older data formats where metadata values were loosely typed before the converter enforced string-only values.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at src/modules/Elsa.Secrets/Models/OrdinalIgnoreCaseDictionaryConverter.cs:33

        var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndObject)
                return values;

            if (reader.TokenType != JsonTokenType.PropertyName)
                throw new JsonException("Expected a metadata property name.");

            var key = reader.GetString()!;
            if (!reader.Read())
                throw new JsonException("Unexpected end of JSON while reading secret metadata.");

            if (reader.TokenType == JsonTokenType.Null)
                continue;

            if (reader.TokenType != JsonTokenType.String)
                throw new JsonException("Expected a string metadata value.");

            var value = reader.GetString();
            if (value != null)
                values[key] = value;
        }

        throw new JsonException("Unexpected end of JSON while reading secret metadata.");
    }

    public override void Write(Utf8JsonWriter writer, IDictionary<string, string> value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        foreach (var item in value.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
            writer.WriteString(item.Key, item.Value);
        writer.WriteEndObject();
    }
}

View on GitHub (pinned to fe9217bdfa)