Skip to content

Allow list fields on map-backed types - #829

Merged
oryan-block merged 2 commits into
masterfrom
bugfix/295
Oct 4, 2026
Merged

oryan-block merged 2 commits into
masterfrom
bugfix/295

Conversation

@oryan-block

Copy link
Copy Markdown
Collaborator

Fixes #295

Checklist

  • Pull requests follows the contribution guide
  • New or modified functionality is covered by tests

Description

A type backed by a Map can't have list fields. With a resolver returning Map<String, Object>, or a HashMap registered in the dictionary (.dictionary("G1", HashMap.class) in the issue), a field like b: [String] fails to build with Java class is not a List or generic type information was lost: class java.lang.Object. MapFieldResolver resolves the value type of an untyped map to Object, and since FieldResolverScanner passes it the raw class, that's also the case for a Map<String, Any>. The ListType branch of TypeClassMatcher#match only accepts a parameterized Iterable or an array, so it throws. Scalar and object type fields already accept Object.

Now when the real type is Object, TypeClassMatcher matches the list's element type as Object too. Scalar elements become a scalar match, and nested lists ([[Int]]) recurse the same way. Object type elements go through the existing Object handling in SchemaClassScanner, so a type in the dictionary works and a missing one gets the usual "maps to a field of type java.lang.Object however there is no matching entry for this type in the type dictionary" error. Maps with a typed list value, like class TagsMap : HashMap<String, List<String>>, still match through the parameterized Iterable branch.

Raw List/Iterable return types (e.g. a Java method returning a raw java.util.List) still fail with "generic type information was lost". That's the lost generics case, not the map one. FieldResolverScanner still passes the raw map class to MapFieldResolver, so a method returning Map<String, List<Key>> still resolves its values to Object and needs Key in the dictionary. I left that alone since it doesn't matter for scalar lists anymore.

Map-typed input objects with a list of input objects (input FooInput { subs: [SubInput] } with a Map<String, Any> argument and SubInput in the dictionary) also still fail with "Java class is not a List". That's a different path: the dictionary fallback in SchemaClassScanner#handleNewType returns the element class but matches it against the list-wrapped type. It's broken on master as well and probably belongs with the input scanning issues (#355, #479, #422). Also, the repro in #437 (a root resolver that extends HashMap, with foo: [FooType]) now builds but still fails at runtime with MapFieldResolver attempt to fetch a field from an object instance that was not a map. That's an older bug in MapFieldResolverDataFetcher. It checks the execution root instead of the source, so even scalar fields on such a resolver fail on master.

Behaviour change: the fix is in TypeClassMatcher, so any Object-typed source now matches a list field, not only map values. That includes resolver methods returning Any/Object, List<*>/List<?> elements against nested lists, and Object parameters for list arguments. These used to fail with "Java class is not a List or generic type information was lost" and now build, same as Object already did for scalar and object type fields. GraphQL object types inside those lists have to be in the dictionary, otherwise the build fails with the existing dictionary error. Schemas that built before aren't affected.

🤖 Generated with Claude Code

oryan-block and others added 2 commits October 3, 2026 18:17
Map-backed types could not declare list fields. MapFieldResolver
resolves the value type of an untyped map to Object, and the ListType
branch of TypeClassMatcher only accepted a parameterized Iterable or an
array, so schema building failed with "Java class is not a List or
generic type information was lost: class java.lang.Object".

Object already matches scalar and object type fields, so a list field
backed by Object now matches its element type as Object too. Scalar
elements produce a scalar match and object type elements go through
the existing dictionary lookup for Object, which still reports a
missing dictionary entry. Maps with a typed list value type keep
matching through the parameterized Iterable branch.

Fixes #295

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 3, 2026

Copy link
Copy Markdown

@oryan-block
oryan-block merged commit 924ad5a into master Oct 4, 2026
6 checks passed
@oryan-block
oryan-block deleted the bugfix/295 branch October 4, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue with Map containing List

1 participant