graphql-java-kickstart / graphql-java-tools

A schema-first tool for graphql-java inspired by graphql-tools for JS
https://www.graphql-java-kickstart.com/tools/
MIT License
812 stars 174 forks source link

Unable to match type definition (ListType{type=NonNullType{type=TypeName{name='String'}}}) with java type (interface java.util.List): Java class is not a List or generic type information was lost: interface java.util.List #415

Open josdejong opened 4 years ago

josdejong commented 4 years ago

I have the following (Kotlin) data class:

data class EntityMatch<T> (
        val text: String,
        val value: T?,
        val start: Int,
        val end: Int
)

Now I have a List<String> as generic type of the data class EntityMatch, like:

data class TestClass (
        val match: EntityMatch<List<String>>
)

and create the GraphQL schema accordingly:

type StringListEntityMatch {
    text: String!
    value: [String!]
    start: Int!
    end: Int!
}

type TestClass {
  match: StringListEntityMatch!
}

I get the following error when starting my application:

...
Caused by: graphql.kickstart.tools.SchemaClassScannerError: Unable to match type definition 
    (ListType{type=NonNullType{type=TypeName{name='String'}}}) with java type (interface java.util.List): 
    Java class is not a List or generic type information was lost: interface java.util.List

I suppose this has to do with using List as a generic type.

I'm using graphql-java-tools:6.1.0, the latest version.

Any idea on how to get this data type working in GraphQL? Thanks!

vojtapol commented 4 years ago

That is not a valid GraphQL schema. Did you mean something like this?

type TestClass {
  match: EntityMatch!
}

type EntityMatch {
  text: String!
  value: [String!]
  start: Int!
  end: Int!
}
josdejong commented 4 years ago

Thanks for your quick reply. Sorry, I tried to simplify the model in this question from a large code base. The actual schema looks like:

type StringListEntityMatch {
    text: String!
    value: [String!]
    start: Int!
    end: Int!
}

type TestClass {
  match: StringListEntityMatch!
}

Will update the original question to prevent confusion.

josdejong commented 4 years ago

FYI: we've tried a few workarounds:

  1. Using Java Array instead of Kotlin List works. However, you lose all the goodness of Kotlin List like deep equality checks etc. In our case this was too painful.

    data class TestClass (
            val match: EntityMatch<Array<String>>
    )
  2. Create a copy of the class having the model defined with Array<String> instead of List<String>, and use this class only in the GraphQL layer and do conversions of the data between the application and the GraphQL layer.
  3. store the data as a comma separated list in a single string, and write a wrapper model Matches to work with it

    data class TestClass (
            val match: EntityMatch<Matches>
    )
    
    data class Matches (
        val matches: String // comma separated list with matches
    )
    
    // create helper functions to create Matches from a List<String>, and to convert Matches into List<String>

In the end we settled with solution (3) which was best for our case.

nnrudakov commented 3 years ago

Another workaround. Instead of:

input MainInput {
    childs: [OneChildInput!]!
}

input OneChildInput {
  id: ID
}

use this:

input MainInput {
    childs: ManyChildsInput!
}

input ManyChildsInput {
  childs: [OneChildInput!]!
}

input OneChildInput {
  id: ID
}

and in code:

class ManyChildsInput(val childs: List<OneChildInput>)