Skip to content

[dotnet] Emit PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays#9957

Merged
JoshLove-msft merged 8 commits into
mainfrom
copilot/update-propagateget-pattern
May 18, 2026
Merged

[dotnet] Emit PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays#9957
JoshLove-msft merged 8 commits into
mainfrom
copilot/update-propagateget-pattern

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 6, 2026

  • Emit PropagateGet pattern (IsEmpty check + TryResolve{Property}Array + Active{Property} helpers) for outermost dynamic-model list/array properties
  • Register new generated methods in BuildMethods()
  • Sync from main
  • Remove changeset file
  • Remove "V2" references from code comments
Original prompt

This section details on the original issue you should resolve

<issue_title>[dotnet] Generator should emit V2 PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays</issue_title>
<issue_description>## Summary

The dotnet generator needs to update the code it produces for PropagateGet methods on models that have array/collection properties backed by CLR types (e.g., IList<T>, IReadOnlyList<T>, arrays). Currently the generated PropagateGet only handles indexed paths (e.g., $.properties.virtualMachines[0].id) but returns false for non-indexed array-level paths (e.g., $.properties.virtualMachines). This prevents JsonPatch.EnumerateArray from working on CLR-backed collections.

Context

PR Azure/azure-sdk-for-net#56826 added the JsonPatch.EnumerateArray API to System.ClientModel. This API iterates over JSON array elements at a given path, yielding each element as ReadOnlyMemory<byte>. For this to work, the PropagateGet callback registered via JsonPatch.SetPropagators must be able to resolve array-level paths — not just indexed element paths.

That PR includes a hand-written AvailabilitySetDataV2 test model demonstrating the required "v2" pattern alongside the existing "v1" AvailabilitySetData model.

What changed: V1 → V2 PropagateGet

The only behavioral difference between V1 and V2 is in PropagateGet. Everything else (PropagateSet, Deserialize, Serialize structure, constructor, properties) is identical.

V1 (current generator output)

private bool PropagateGet(ReadOnlySpan<byte> jsonPath, out JsonPatch.EncodedValue value)
{
    ReadOnlySpan<byte> local = jsonPath.SliceToStartOfPropertyName();
    value = default;

    // ... other property branches ...

    else if (local.StartsWith("properties.virtualMachines"u8))
    {
        int propertyLength = "properties.virtualMachines"u8.Length;
        ReadOnlySpan<byte> indexSlice = local.Slice(propertyLength);

        // V1: only handles indexed paths — falls through to TryGetIndex
        // which returns false when indexSlice is empty (no index)
        if (!SerializationHelpers.TryGetIndex(indexSlice, out int index, out int bytesConsumed))
            return false;

        if (VirtualMachines.Count > index)
            return VirtualMachines[index].Patch.TryGetEncodedValue(
                [.. "$"u8, .. indexSlice.Slice(bytesConsumed + 2)], out value);
    }

    return false;
}

V2 (what the generator should produce)

private bool PropagateGet(ReadOnlySpan<byte> jsonPath, out JsonPatch.EncodedValue value)
{
    ReadOnlySpan<byte> local = jsonPath.SliceToStartOfPropertyName();
    value = default;

    // ... other property branches ...

    else if (local.StartsWith("properties.virtualMachines"u8))
    {
        int propertyLength = "properties.virtualMachines"u8.Length;
        ReadOnlySpan<byte> indexSlice = local.Slice(propertyLength);

        // V2 ENHANCEMENT: handle array-level path (no index) by serializing CLR collection
        if (indexSlice.IsEmpty)
        {
            return TryResolveVirtualMachinesArray(out value);
        }

        if (!SerializationHelpers.TryGetIndex(indexSlice, out int index, out int bytesConsumed))
            return false;

        if (VirtualMachines.Count > index)
            return VirtualMachines[index].Patch.TryGetEncodedValue(
                [.. "$"u8, .. indexSlice.Slice(bytesConsumed + 2)], out value);
    }

    return false;
}

Plus these supporting methods per array property:

private bool TryResolveVirtualMachinesArray(out JsonPatch.EncodedValue value)
{
    value = default;
    BinaryData data = ModelReaderWriter.Write(
        ActiveVirtualMachines(), new ModelReaderWriterOptions("J"));
    var tempPatch = new JsonPatch();
    tempPatch.Set("$"u8, data.ToMemory().Span);
    return tempPatch.TryGetEncodedValue("$"u8, out value);
}

private IEnumerable<WritableSubResource> ActiveVirtualMachines()
{
    if (!OptionalProperty.IsCollectionDefined(VirtualMachines))
        yield break;
    for (int i = 0; i < VirtualMachines.Count; i++)
    {
        if (!VirtualMachines[i].Patch.IsRemoved("$"u8))
            yield return VirtualMachines[i];
    }
}

Why this matters

Without the V2 pattern, JsonPatch.EnumerateArray("$.properties.virtualMachines"u8) cannot resolve the array from CLR data when no patch-level replacement exists at that path. The propagator returns false, so EnumerateArray has no array data to iterate over.

What the generator needs to do

For every collection/array property that has a PropagateGet branch:

  1. Add an indexSlice.IsEmpty check before the TryGetIndex call
  2. Generate a TryResolve{PropertyName}Array method that serializes the active (non-removed) CLR items via ModelReaderWriter.Write and returns the result as an EncodedValue
  3. *Generate an Active{PropertyName} iterator...

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…rt on CLR arrays

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Mar 6, 2026
Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copilot AI changed the title [WIP] Update generator to support V2 PropagateGet pattern for JsonPatch [dotnet] Emit V2 PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays Mar 6, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added the stale Mark a PR that hasn't been recently updated and will be closed. label Apr 6, 2026
@JoshLove-msft JoshLove-msft reopened this May 16, 2026
@JoshLove-msft
Copy link
Copy Markdown
Contributor

@copilot sync from main

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 16, 2026

@copilot sync from main

Synced from main in 24f32cf. The merge was clean with no conflicts.

Comment thread .chronus/changes/copilot-update-propagateget-pattern-2026-03-06-20-49-06.md Outdated
Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/02517a09-2cfe-4a41-8875-275117f0d120

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/dae84b6b-f66a-4495-aa25-d9e0116dc61f

Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 16, 2026

@copilot sync from main

Branch is already up to date with main (no new commits since the previous sync in 24f32cf).

@JoshLove-msft JoshLove-msft marked this pull request as ready for review May 16, 2026 02:05
Copilot AI changed the title [dotnet] Emit V2 PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays [dotnet] Emit PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays May 16, 2026
Copilot AI requested a review from JoshLove-msft May 16, 2026 02:05
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 18, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@9957

commit: 6e01da7

@github-actions
Copy link
Copy Markdown
Contributor

No changes needing a change description found.

@JoshLove-msft JoshLove-msft enabled auto-merge May 18, 2026 20:08
@JoshLove-msft JoshLove-msft added this pull request to the merge queue May 18, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 18, 2026
@JoshLove-msft JoshLove-msft added this pull request to the merge queue May 18, 2026
Merged via the queue into main with commit 3311b24 May 18, 2026
29 checks passed
@JoshLove-msft JoshLove-msft deleted the copilot/update-propagateget-pattern branch May 18, 2026 22:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp stale Mark a PR that hasn't been recently updated and will be closed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[dotnet] Generator should emit V2 PropagateGet pattern to support JsonPatch.EnumerateArray on CLR arrays

3 participants