elsa-workflows/elsa-core · error · JsonException
Expected number or string.
Error message
Expected number or string.
What it means
Thrown by DecimalJsonConverter.Read when the JSON token being deserialized into a decimal is neither a JSON number nor a JSON string. It is a generic guard: tokens such as true, false, null, StartObject or StartArray are rejected; only numbers (read directly) and invariant-culture-parsable strings are accepted.
Solutions
- Send a JSON number or a plain invariant numeric string like "12.34".
- Handle JSON null by making the target property decimal? or adding null handling to the converter.
- Pre-normalize strings (strip separators, use '.' decimal point) before sending.
- Wrap parsing with decimal.TryParse and throw a descriptive JsonException including the raw value.
Example fix
// before
{"amount": null}
// after (property becomes decimal?)
public decimal? Amount { get; set; } Defensive patterns
Strategy: validation
Validate before calling
if (node.ValueKind is not (JsonValueKind.Number or JsonValueKind.String))
throw new FormatException($"Expected decimal, got {node.ValueKind}");
if (node.ValueKind == JsonValueKind.String && !decimal.TryParse(node.GetString(), NumberStyles.Number, CultureInfo.InvariantCulture, out _))
throw new FormatException("String is not an invariant decimal"); Type guard
bool IsJsonDecimal(JsonElement e) => e.ValueKind == JsonValueKind.Number || (e.ValueKind == JsonValueKind.String && decimal.TryParse(e.GetString(), NumberStyles.Number, CultureInfo.InvariantCulture, out _));
Try / catch
try { amount = JsonSerializer.Deserialize<decimal>(json, options); }
catch (JsonException ex) { errors.Add($"amount: {ex.Message}"); } Prevention
- Use InvariantCulture for all numeric wire formats
- Reject thousand separators and localized decimal points at ingestion
- Use decimal? when the field may be absent/null
When it happens
Trigger: Deserializing a decimal property from a JSON token that is not a number and not a string — e.g. null, true, an object, or an array; also decimal.Parse throws FormatException for a malformed numeric string.
Common situations: Nullable decimal fields receiving JSON null; clients sending "1,234.56" with thousands separators or comma decimal separators; nested objects where a scalar was expected.
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
- Cannot deserialize to .
- Failed to deserialize
- The binding to activity type
- Expected number or string.
- Cannot convert to bool
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/a090a705b2937a8c.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Common/Converters/DecimalJsonConverter.cs:24
/// <summary>
/// Converts decimals to and from JSON strings.
/// </summary>
public class DecimalJsonConverter : JsonConverter<decimal>
{
/// <inheritdoc />
public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number)
return reader.GetDecimal();
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString()!;
return decimal.Parse(value, CultureInfo.InvariantCulture);
}
throw new JsonException("Expected number or string.");
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)
{
// Write the decimal as a JSON number
writer.WriteNumberValue(value);
}
}View on GitHub (pinned to fe9217bdfa)