{"record":{"id":"a090a705b2937a8c","repo":"elsa-workflows/elsa-core","slug":"expected-number-or-string-decimaljsonconverter","errorCode":null,"errorMessage":"Expected number or string.","messagePattern":"Expected number or string\\.","errorType":"validation","errorClass":"JsonException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Common/Converters/DecimalJsonConverter.cs","lineNumber":24,"sourceCode":"\n/// <summary>\n/// Converts decimals to and from JSON strings.\n/// </summary>\npublic class DecimalJsonConverter : JsonConverter<decimal>\n{\n    /// <inheritdoc />\n    public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)\n    {\n        if (reader.TokenType == JsonTokenType.Number)\n            return reader.GetDecimal();\n\n        if (reader.TokenType == JsonTokenType.String)\n        {\n            var value = reader.GetString()!;\n            return decimal.Parse(value, CultureInfo.InvariantCulture);\n        }\n\n        throw new JsonException(\"Expected number or string.\");\n    }\n\n    /// <inheritdoc />\n    public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options)\n    {\n        // Write the decimal as a JSON number\n        writer.WriteNumberValue(value);\n    }\n}","sourceCodeStart":6,"sourceCodeEnd":33,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Common/Converters/DecimalJsonConverter.cs#L6-L33","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{\"amount\": null}\n// after (property becomes decimal?)\npublic decimal? Amount { get; set; }","handlingStrategy":"validation","validationCode":"if (node.ValueKind is not (JsonValueKind.Number or JsonValueKind.String))\n    throw new FormatException($\"Expected decimal, got {node.ValueKind}\");\nif (node.ValueKind == JsonValueKind.String && !decimal.TryParse(node.GetString(), NumberStyles.Number, CultureInfo.InvariantCulture, out _))\n    throw new FormatException(\"String is not an invariant decimal\");","typeGuard":"bool IsJsonDecimal(JsonElement e) => e.ValueKind == JsonValueKind.Number || (e.ValueKind == JsonValueKind.String && decimal.TryParse(e.GetString(), NumberStyles.Number, CultureInfo.InvariantCulture, out _));","tryCatchPattern":"try { amount = JsonSerializer.Deserialize<decimal>(json, options); }\ncatch (JsonException ex) { errors.Add($\"amount: {ex.Message}\"); }","preventionTips":["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"],"tags":["json","deserialization","decimal"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}