elsa-workflows / elsa-workflows/elsa-core
ObjectConverter static defaults + InternalSerializerOptions strip DI converters; Api.Client fork diverged
- Dominant language
- C#
- Stars
- 7.9k
- Forks
- 1.5k
- Avg merge
- 15h 22m
- Merged PRs (30d)
- 114
Description
## Problem
Even after #8158 fixed `GetInput` options resolution, the conversion *engine* still has process-wide static serializer defaults and an inner path that **drops** caller-supplied converters. A second, diverged `ObjectConverter` lives in `Elsa.Api.Client`. Custom converters remain inconsistent depending on which overload/path runs.
## Evidence
### 1. Static `DefaultSerializerOptions` (Expressions)
`src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs`:
```csharp
private static JsonSerializerOptions? _defaultSerializerOptions;
private static JsonSerializerOptions DefaultSerializerOptions => _defaultSerializerOptions ??= new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
/* JsonStringEnumConverter only — no host converters */
};
var serializerOptions = converterOptions?.SerializerOptions ?? DefaultSerializerOptions;
```
Callers that pass **no** `ObjectConverterOptions` hit this forever after first use, e.g.:
- `Variable.Get` / `Variable.Get` → `ConvertTo()`
- `MemoryBlockReference.Get`
- C#/Python `ExecutionContextProxy` / `OutputProxy`
- `LiquidExpressionHandler` → `renderedString.ConvertTo(returnType)`
- `LiteralExpressionHandler` (WellKnownTypeRegistry only; still DefaultSerializerOptions for JSON shapes)
### 2. `InternalSerializerOptions` strips converters on dictionary→POCO
Same file — `IDictionary` / `ExpandoObject` → class/interface:
```csharp
var internalSerializerOptions = InternalSerializerOptions; // Encoder only
var json = JsonSerializer.Serialize(sourceDictionary, internalSerializerOptions);
return ConvertTo(json, underlyingTargetType, converterOptions);
```
Serialize step ignores `converterOptions.SerializerOptions`. Types that need a custom **Write** converter are wrong before deserialize can help.
### 3. Diverged Api.Client fork
`src/clients/Elsa.Api.Client/Extensions/ObjectConverter.cs` is a separate copy without:
- `StrictMode` / `ObjectConverterOptions.StrictMode`
- `WellKnownTypeRegistry` / `DeserializeJsonObjectToObject`
- `DateOnly`, richer `JsonNode`/`JsonArray` handling, `TypeConversionException`
Behavior differs (e.g. invalid `TypeConverter` → `GetDefaultValue()` on client vs throw/StrictMode on server). Two algorithms to maintain.
## Why it matters architecturally
#7194 / sibling ObjectExpressionHandler fixes that “pass host options into ConvertTo” only help when callers pass options. Hot paths still use bare `ConvertTo()`. Dictionary→typed conversion can still strip converters. Client/server drift invites “works in Studio client, fails on server” (or the reverse).
Subtractive clarity: **one** conversion helper, **one** options source (host or explicit), no silent inner options that discard converters.
## Proposed subtractive direction
1. Prefer deleting `DefaultSerializerOptions` process cache — require explicit options, or resolve from a single ambient/host accessor used by Expressions + Workflows.Core (not a third static).
2. For dictionary/Expando → POCO, serialize with the **same** `SerializerOptions` as deserialize (or skip JSON round-trip if a subtractive alternative exists).
3. Collapse or generate Api.Client `ObjectConverter` from the Expressions implementation (or shared package); stop hand-forking StrictMode/DateOnly/JsonNode behavior.
4. Audit bare `ConvertTo` / `ConvertTo` call sites in expression proxies and Liquid; pass host options or document that those paths are TypeConverter/`ChangeType` only.
Prefer deletion/unification over a new conversion abstraction layer.
## Not a duplicate of
- #7194 — VariableExtensions hardcoded options (consumer; this is the engine + fork)
- #8131 (closed) — GetInput static cache only
- ObjectExpressionHandler hardcoded options (sibling consumer — cite together)
- #8046 — expression-descriptor fail-open on definition JSON
- #6745 — ObjectFormatter / VariableMapper
- #6978 — ExpandoObject list on WorkflowInstanceStorageDriver
- #7770 — new output-converter feature
- #2412 — Elsa 2–era serialization handlers (stale enhancement)
## Milestone
Unset — Triage / Crew Lead place. Do not auto-assign Engineer. Do not add Ready.
Contributor guide
Research direction
Start with src/modules/Elsa.Expressions/Helpers/ObjectConverter.cs and src/clients/Elsa.Api.Client/Extensions/ObjectConverter.cs, then audit the bare ConvertTo call sites named in the issue. Trace serializer options through dictionary/ExpandoObject conversion and the expression proxies and Liquid handler. Done means conversion uses one explicit or host-resolved options source, preserves converters on inner serialization, and removes client/server behavioral drift.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100