Repository navigation
feat: the binder bridges a collection of collections - #167
Merged
ShocOne merged 1 commit intoAug 30, 2026
Merged
Conversation
The SDK binding layer and the conversion catalog carry a list of lists, a list of maps, a map of lists and a map of maps under both dialects, to any depth with a scalar at the bottom. The binder copies the derivation's levels onto the field binding and drafts one plain SDK type composed to depth — [][]string, map[string][]int64 — with one conversion pair per direction, FromNestedCollectionSlice/Map and ToNestedCollectionSlice/Map, that takes the whole value and the framework element type. Pruning settles that draft against whichever of four carriers the SDK actually has: a plain Go value, matched level by level so the framework's reflection can never meet a shape the binder did not approve; one kiota model whose only content is an additionalData bag, which is how kiota carries a map of lists or of maps; a slice of such models, which is how it carries a list of maps; and kiota's untyped node, matched on its package path, which is how it carries a list of lists. An SDK type that does not nest as the document declares is excluded naming the declared shape. nestedModelOf unwraps every collection level, so an object at the bottom resolves against its model. The catalog gains the helpers those shorthands name. The plain pair hands the value to types.ListValueFrom / ElementsAs, whose reflection already walks nested Go values. The kiota pairs walk a bag against the schema's element type: a parsed response carries arrays as []any, objects as map[string]any and scalars as pointers, every number a float64; a map entry of the wrong shape is skipped alone, and a list element of the wrong shape becomes the typed null so the elements after it keep their positions. The untyped-node pair unwraps kiota's node kinds to the same plain values and wraps them again on the way out. bagString and bagBool join bagInt64 and bagFloat64 as the one leaf rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XPLePCqzyYncF7TedagFkg
ShocOne
deleted the
feat/the-binder-bridges-a-collection-of-collections
branch
August 30, 2026 09:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cut from
mainafter #166. Second of the three nested-collection PRs; the emit + fixtures + curated-fixture PR follows once this merges.What
The SDK binding layer and the conversion catalog carry list of lists, list of maps, map of lists and map of maps — and deeper, with a scalar at the bottom — under both dialects. Nothing generates differently yet: derivation still excludes depth ≥ 2 under
nestedCollectionElementuntil the emit PR removes that gate, so this lands the machinery with its tests and changes no generated tree.Binder (
internal/sdkbind)FieldBinding.NestedCollectionElementTypescopies the derivation's levels.nestedCollectionGoType:[][]string,map[string]map[string]int64) and one conversion pair,FromNestedCollectionSlice/Map/ToNestedCollectionSlice/Map.prune_collections.go:settleCollectionsettles the draft against the carrier the SDK actually has, in order:serialization.UntypedNodeable, matched on package path likeisKiotaDateOnly) →…SliceUntypedNode; a map declared over one is excluded.GetAdditionalData) for a map of lists / map of maps →…MapAdditionalData, model + write constructor carried (the existing scalar bag arm now sharessettleBag).…SliceAdditionalData.plainCollectionMatches: slice ↔ list, string-keyed map ↔ map, compatible basic leaf; a pointer at any level, a named leaf or a non-string key does not match) →…Slice/Map.unbridgeableType, the reason naming the declared shape ("does not nest the way the document declares a map of list of string").nestedModelOfunwraps every collection level, so an object at the bottom of a nested collection resolves against its model.Conversion catalog (templates)
APIToFrameworkNestedCollectionList/Map(ctx, values any, elementType attr.Type)andFrameworkToAPINestedCollectionList/Map[T any](ctx, value, setter func(T))— the framework's own reflection walks nested Go values; nil at any depth is null, empty stays empty.nested_collections_kiota.go.tmpl:…MapAdditionalData,…ListAdditionalData[T](one model per element, built through aconstruct func() T),…ListUntypedNode; walkersframeworkValueFromBag/bagValueFromFramework/anyFromUntypedNode/untypedNodeFromAny. Rules: a parsed bag holds[]any,map[string]anyand pointer scalars with every number afloat64; a stray map entry is skipped alone (as test: the curated fixture carries a kiota additionalData bag #162 decided), a stray list element becomes the typed null so positions hold;nilis the typed null at every level.bagString/bagBoolextracted so the four scalar bag helpers and the walkers share one leaf rule.SDK shapes this was verified against
Generated from a probe spec with kiota 1.34.1 and openapi-generator (scratchpad, not committed): openapi-generator emits
[][]string,[]map[string]string,map[string][]string,map[string]map[string]string; kiota emitsUntypedNodeablefor a list of lists,[]Thing_listOfMapsablefor a list of maps, and a singleThing_mapOfLists/Thing_mapOfMapsbag model for the map forms.GetRawValueinkiota-serialization-json-go@v1.1.4unwraps nested arrays/objects recursively;WriteAdditionalDatamarshals[]any/map[string]anythroughjson.Marshal.Tests
prune_collections_test.go: every pairing at two levels and one at three through the plain arm; seven shapes that must not match (short, deep, map-for-list, pointer, non-string key, named leaf, incompatible leaf); map of lists through a bag; list of bags with the constructor; list of lists through the untyped node (and a foreignUntypedNodeablenot matching);nestedModelOfto depth;nestedCollectionGoType/describeCollection.TestUnit_Run_CuratedTreeCompiles: plain round trips for all four shapes, nil/empty/null/unknown rules; kiota bag forms, stray-entry vs stray-element rules, bag round trip, one-model-per-element, untyped-node round trip and a scalar node reading as null.Verification
make checkgreen: sdkbind 83.3% (from 82.6%), 90.2% total, hygiene gate clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01XPLePCqzyYncF7TedagFkg