Skip to content

Commit c8a7fb9

Browse files
oryan-blockclaude
andcommitted
Allow list fields on property map types
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>
1 parent 62c5146 commit c8a7fb9

2 files changed

Lines changed: 132 additions & 0 deletions

File tree

‎src/main/kotlin/graphql/kickstart/tools/TypeClassMatcher.kt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
7070
match(potentialMatch, graphQLType.type, realType.actualTypeArguments.first())
7171
} else if (realType is Class<*> && realType.isArray) {
7272
match(potentialMatch, graphQLType.type, realType.componentType)
73+
} else if (realType == Object::class.java) {
74+
// e.g. a property map value: the element type is unknown, so match it as Object too
75+
match(potentialMatch, graphQLType.type, realType)
7376
} else {
7477
throw error(potentialMatch, "Java class is not a List or generic type information was lost: $realType")
7578
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package graphql.kickstart.tools
2+
3+
import graphql.GraphQL
4+
import org.junit.Assert.assertThrows
5+
import org.junit.Test
6+
7+
class PropertyMapListTest {
8+
9+
@Test
10+
fun `property maps should support list fields`() {
11+
val schema = SchemaParser.newParser()
12+
.schemaString(
13+
"""
14+
type Query {
15+
greeting: Greeting!
16+
}
17+
18+
type Greeting {
19+
name: String!
20+
tags: [String!]!
21+
matrix: [[Int]]
22+
key: Key
23+
keys: [Key!]
24+
}
25+
26+
type Key {
27+
a: String
28+
b: [String]
29+
}
30+
""")
31+
.resolvers(object : GraphQLQueryResolver {
32+
fun greeting(): Map<String, Any> = mapOf(
33+
"name" to "hello",
34+
"tags" to listOf("x", "y"),
35+
"matrix" to listOf(listOf(1, 2), listOf(3)),
36+
"key" to hashMapOf("a" to "one", "b" to listOf("ss")),
37+
"keys" to listOf(hashMapOf("a" to "two", "b" to listOf("tt")))
38+
)
39+
})
40+
.dictionary("Key", HashMap::class)
41+
.build()
42+
.makeExecutableSchema()
43+
val gql = GraphQL.newGraphQL(schema).build()
44+
45+
val data = assertNoGraphQlErrors(gql) {
46+
"""
47+
{
48+
greeting {
49+
name
50+
tags
51+
matrix
52+
key { a b }
53+
keys { a b }
54+
}
55+
}
56+
"""
57+
}
58+
59+
assertEquals(data["greeting"], mapOf(
60+
"name" to "hello",
61+
"tags" to listOf("x", "y"),
62+
"matrix" to listOf(listOf(1, 2), listOf(3)),
63+
"key" to mapOf("a" to "one", "b" to listOf("ss")),
64+
"keys" to listOf(mapOf("a" to "two", "b" to listOf("tt")))
65+
))
66+
}
67+
68+
@Test
69+
fun `property maps with typed list values should support list fields`() {
70+
val schema = SchemaParser.newParser()
71+
.schemaString(
72+
"""
73+
type Query {
74+
tags: Tags!
75+
}
76+
77+
type Tags {
78+
names: [String!]!
79+
}
80+
""")
81+
.resolvers(object : GraphQLQueryResolver {
82+
fun tags(): TagsMap = TagsMap().apply { put("names", listOf("x", "y")) }
83+
})
84+
.build()
85+
.makeExecutableSchema()
86+
val gql = GraphQL.newGraphQL(schema).build()
87+
88+
val data = assertNoGraphQlErrors(gql) {
89+
"""
90+
{
91+
tags {
92+
names
93+
}
94+
}
95+
"""
96+
}
97+
98+
assertEquals(data["tags"], mapOf("names" to listOf("x", "y")))
99+
}
100+
101+
@Test
102+
fun `property maps with object list fields should require a dictionary entry`() {
103+
val error = assertThrows(SchemaClassScannerError::class.java) {
104+
SchemaParser.newParser()
105+
.schemaString(
106+
"""
107+
type Query {
108+
greeting: Greeting!
109+
}
110+
111+
type Greeting {
112+
keys: [Key!]
113+
}
114+
115+
type Key {
116+
a: String
117+
}
118+
""")
119+
.resolvers(object : GraphQLQueryResolver {
120+
fun greeting(): Map<String, Any> = mapOf()
121+
})
122+
.build()
123+
}
124+
125+
assertEquals(error.message, "The GraphQL schema type 'Key' maps to a field of type java.lang.Object however there is no matching entry for this type in the type dictionary. You may need to add this type to the dictionary before building the schema.")
126+
}
127+
128+
class TagsMap : HashMap<String, List<String>>()
129+
}

0 commit comments

Comments
 (0)