Skip to content

.NET: [Bug]: Declarative workflow - JSON arrays of objects parsed as empty records when no schema is defined #4195

@vvotekeb

Description

@vvotekeb

Description

When an InvokeAzureAgent action returns a JSON object containing an array of objects, and the output is captured via responseObject, all nested object properties are silently dropped. Each object in the array becomes {}. Subsequent Foreach iteration over the array crashes with an unhandled workflow failure.

This affects any schema-less JSON parsing path in declarative workflows, not only InvokeAzureAgent.

To Reproduce

1. Workflow YAML

kind: Workflow
name: repro-empty-records
trigger:
  kind: OnConversationStart
  id: trigger
  actions:
    - kind: InvokeAzureAgent
      id: invoke_agent
      agent:
        name: SomeAgent
      conversationId: =System.ConversationId
      input:
        messages: =[UserMessage("list team members")]
      output:
        responseObject: Local.agent_output
    - kind: SendActivity
      id: show_items
      activity: "{Local.agent_output.items}"
    - kind: Foreach
      id: iterate_items
      items: =Local.agent_output.items
      value: Local.currentItem
      index: Local.index
      actions:
        - kind: SendActivity
          id: show_item
          activity: "{Local.currentItem}"
    - kind: EndWorkflow
      id: end

2. Agent JSON response (last message text)

{
  "items": [
    { "name": "Alice", "role": "Engineer" },
    { "name": "Bob", "role": "Designer" },
    { "name": "Carol", "role": "PM" }
  ]
}

3. Expected behavior

SendActivity at show_items should display the full array with object properties intact. Foreach should iterate and Local.currentItem should contain each record with name and role fields.

4. Actual behavior

  • SendActivity outputs: [ {}, {}, {} ] — all object properties are lost.
  • Foreach crashes: Unhandled workflow failure - #iterate_items (Foreach) — because ForeachExecutor calls .Properties.Values.First() on records that have zero properties.

Root Cause

The bug is in JsonDocumentExtensions.cs, inside the ParseTable method's local function DetermineElementType():

// Line ~114
JsonValueKind.Object => VariableType.Record(targetType.Schema?.Select(kvp => (kvp.Key, kvp.Value)) ?? []),

When targetType.Schema is null (no schema defined — always the case for responseObject since it is parsed with VariableType.RecordType), the ?? [] fallback passes an empty collection to VariableType.Record(). This creates a VariableType with Schema set to an empty FrozenDictionary (non-null, count = 0).

Downstream, ParseRecord checks targetType.Schema is null to decide between dynamic parsing (ParseValues()) and schema-based parsing (ParseSchema()):

// Lines ~47-50
IEnumerable<KeyValuePair<string, object?>> keyValuePairs =
    targetType.Schema is null ?
    ParseValues() :           // <-- dynamic: preserves all JSON properties
    ParseSchema(targetType.Schema);  // <-- schema-based: iterates schema fields only

Because Schema is an empty dictionary (not null), it takes the ParseSchema path, iterates over zero schema fields, and returns an empty dictionary for every object in the array. All JSON properties are silently discarded.

Note that VariableType.HasSchema correctly handles this ((this.Schema?.Count ?? 0) > 0 returns false for empty schemas), but ParseRecord uses a null check instead of HasSchema.

Cascade to ForeachExecutor

ForeachExecutor.ExecuteAsync extracts values via:

this._values = [.. tableValue.Values.Select(value => value.Properties.Values.First().ToFormula())];

Since each RecordDataValue has empty Properties, .First() throws, producing the unhandled workflow failure.

Proposed Fix

In JsonDocumentExtensions.cs, DetermineElementType(), change the JsonValueKind.Object case to avoid creating a schema-bound VariableType when there is no schema to propagate:

- JsonValueKind.Object => VariableType.Record(targetType.Schema?.Select(kvp => (kvp.Key, kvp.Value)) ?? []),
+ JsonValueKind.Object => targetType.HasSchema
+     ? VariableType.Record(targetType.Schema!.Select(kvp => (kvp.Key, kvp.Value)))
+     : VariableType.RecordType,

When HasSchema is false, this uses VariableType.RecordType (typeof(IDictionary<string, object?>) with Schema = null), so ParseRecord takes the dynamic ParseValues() path and preserves all JSON properties.

Related

Code Sample

Error Messages / Stack Traces

Package Versions

Microsoft.Agents.AI.Abstractions:1.0.0-preview.260212.1, Microsoft.Agents.AI.Declarative:1.0.0-preview.260212.1, Microsoft.Agents.AI.OpenAI:1.0.0-preview.260212.1, Microsoft.Agents.AI.Workflows:1.0.0-preview.260212.1, Microsoft.Agents.AI.Workflows.Declarative:1.0.0-preview.260212.1

.NET Version

.NET 8.0

Additional Context

No response

Metadata

Metadata

Assignees

Labels

.NETbugSomething isn't workingdeclarative-workflowv1.0Features being tracked for the version 1.0 GA

Type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions