elsa-workflows / elsa-workflows/elsa-core

ObjectExpressionHandler hardcodes JsonSerializerOptions (Object/Json expressions ignore DI converters)

Open
#8,164 0 comments 0 reactions 0 assignees View on GitHub
bug core elsa 3 prio low tech debt triaged
Dominant language
C#
Stars
7.9k
Forks
1.5k
Avg merge
15h 22m
Merged PRs (30d)
114

Description

## Problem

`ObjectExpressionHandler` (expression types **Object** and **Json**) builds a private, instance-cached `JsonSerializerOptions` with a fixed converter set and never consults host/DI `IJsonSerializer` / `ISerializationOptionsConfigurator`. Custom `JsonConverter`s registered by the host are ignored for Object/Json expression evaluation.

This is the same bug *class* as #7194 (`VariableExtensions`) and closed #8131 (`GetInput` static cache, fixed in #8158), but a **different entry point**.

## Evidence (main)

`src/modules/Elsa.Workflows.Core/Expressions/ObjectExpressionHandler.cs`:

```csharp
private JsonSerializerOptions? _serializerOptions;

private JsonSerializerOptions SerializerOptions =>
_serializerOptions ??= new JsonSerializerOptions { /* camelCase, Preserve, … */ }
.WithConverters(
new IntegerJsonConverter(),
new DecimalJsonConverter(),
new JsonStringEnumConverter());

public ValueTask EvaluateAsync(...)
{
var converterOptions = new ObjectConverterOptions(SerializerOptions);
var model = value.ConvertTo(returnType, converterOptions);

}
```

Registered for both descriptors in `DefaultExpressionDescriptorProvider` (`CreateObjectDescriptor` / `CreateJsonDescriptor`).

Contrast after #8158 — `ExpressionExecutionContextExtensions.GetInput`:

```csharp
return context.GetRequiredService().GetOptions().CloneForValueConversion();
```

Storage driver already uses `payloadSerializer.GetOptions().Clone()`.

## Why it matters

Three value-conversion stacks remain inconsistent after #8158:

| Path | Options source |
|------|----------------|
| `GetInput` | Host DI (`IJsonSerializer`) ✅ |
| `WorkflowInstanceStorageDriver` | Host DI ✅ |
| `VariableExtensions.ParseValue` | Hardcoded static (#7194) |
| `ObjectExpressionHandler` | Hardcoded private cache (**this**) |

Object/Json literals with custom-typed shapes fail or mis-convert while the same type works via `GetInput` / storage.

## Proposed subtractive direction

1. Resolve options from `context.GetRequiredService().GetOptions().CloneForValueConversion()` (same helper as `GetInput`).
2. Drop the private `_serializerOptions` field; keep Integer/Decimal converters only if still needed after host options (prefer host registration over a second hardcode).
3. Share one small helper with the eventual #7194 fix so Object/Json/Variable parse are not three one-offs.
4. Test: register a custom converter via `ISerializationOptionsConfigurator`; evaluate an Object/Json expression targeting that type — must succeed.

## Not a duplicate of

- #7194 — `VariableExtensions` static options (sibling; cite together, fix separately or with shared helper)
- #8131 (closed) / PR #8158 — `GetInput` only
- #8046 — unknown expression type fail-open on definition deserialize
- #6745 — `ObjectFormatter` / VariableMapper round-trip
- #7770 — activity *output* converter feature (new capability, not this path)

## Milestone

Unset — Triage / Crew Lead place. Do not auto-assign Engineer. Do not add Ready.

Contributor guide

Open the contributing guide

Research direction

Start with src/modules/Elsa.Workflows.Core/Expressions/ObjectExpressionHandler.cs and compare its cached SerializerOptions with ExpressionExecutionContextExtensions.GetInput and the storage driver’s options handling. Trace CreateObjectDescriptor and CreateJsonDescriptor, then add coverage that configures a custom converter through ISerializationOptionsConfigurator and evaluates an Object or Json expression; done means the converter is honored.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.