apollographql / apollo-kotlin

:rocket:  A strongly-typed, caching GraphQL client for the JVM, Android, and Kotlin multiplatform.
https://www.apollographql.com/docs/kotlin
MIT License
3.75k stars 650 forks source link

Failed to parse http response Caused by: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to java.util.List #3467

Closed adam-weissert closed 2 years ago

adam-weissert commented 2 years ago

Summary Hi all. I'm working with an action endpoint from Hasura and for some reason the response appears to be failing during parsing when apollo receives the data callback. Checking our backend, it's clear the response is giving valid data, but it would appear the type is returning as a LinkedHashMap as opposed to a list. Normally this would be simply to isolate and solve, but we have several other endpoints that perform similar actions that return their response without error. The error appears to occur during the readList() function and as such the query invocation never completes.

Version 2.5.9

Description

This error occurs when I invoke this query as a standard query, here's that:

 val voicemailArray = voicemails.getJSONObject(i)
parentCallSid = voicemailArray.getString("parentCallSid")
if(parentCallSid.isNotEmpty()){
       val parentCallLogResponse = apolloClient.query(GetParentCallLogsQuery(parentCallSid)).await()

      Log.d(TAG, parentCallLogResponse.toString())
}

I've attempted to re-pull the schema file and do a clean build of the project, but it results in the same error.

Something worth noting is that we have a different version of this running in our web client through svelte, and the response doesn't seem to cause any type issues.

Here's the full error stack:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.round2pos.phone, PID: 17120
    com.apollographql.apollo.exception.ApolloParseException: Failed to parse http response
        at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor.parse(ApolloParseInterceptor.java:104)
        at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor$1.onResponse(ApolloParseInterceptor.java:53)
        at com.apollographql.apollo.internal.interceptor.ApolloServerInterceptor$executeHttpCall$1.onResponse(ApolloServerInterceptor.kt:114)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
     Caused by: java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to java.util.List
        at com.apollographql.apollo.internal.response.RealResponseReader.readList(RealResponseReader.kt:129)
        at com.apollographql.apollo.api.internal.ResponseReader$DefaultImpls.readList(ResponseReader.kt:44)
        at com.apollographql.apollo.internal.response.RealResponseReader.readList(RealResponseReader.kt:15)
        at com.round2pos.phone.GetParentCallLogsQuery$Parent_call_logs$Companion.invoke(GetParentCallLogsQuery.kt:122)
        at com.round2pos.phone.GetParentCallLogsQuery$Data$Companion$invoke$1$parent_call_logs$1.invoke(GetParentCallLogsQuery.kt:156)
        at com.round2pos.phone.GetParentCallLogsQuery$Data$Companion$invoke$1$parent_call_logs$1.invoke(GetParentCallLogsQuery.kt:155)
        at com.apollographql.apollo.api.internal.ResponseReader$readObject$1.read(ResponseReader.kt:26)
        at com.apollographql.apollo.internal.response.RealResponseReader.readObject(RealResponseReader.kt:118)
        at com.apollographql.apollo.api.internal.ResponseReader$DefaultImpls.readObject(ResponseReader.kt:24)
        at com.apollographql.apollo.internal.response.RealResponseReader.readObject(RealResponseReader.kt:15)
        at com.round2pos.phone.GetParentCallLogsQuery$Data$Companion.invoke(GetParentCallLogsQuery.kt:155)
        at com.round2pos.phone.GetParentCallLogsQuery$responseFieldMapper$$inlined$invoke$1.map(ResponseFieldMapper.kt:22)
        at com.apollographql.apollo.response.OperationResponseParser$1.read(OperationResponseParser.java:97)
        at com.apollographql.apollo.api.internal.json.ResponseJsonStreamReader.nextObject(ResponseJsonStreamReader.kt:59)
        at com.apollographql.apollo.response.OperationResponseParser.parse(OperationResponseParser.java:92)
        at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor.parse(ApolloParseInterceptor.java:87)
        at com.apollographql.apollo.internal.interceptor.ApolloParseInterceptor$1.onResponse(ApolloParseInterceptor.java:53) 
        at com.apollographql.apollo.internal.interceptor.ApolloServerInterceptor$executeHttpCall$1.onResponse(ApolloServerInterceptor.kt:114) 
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at java.lang.Thread.run(Thread.java:923) 
I/Process: Sending signal. PID: 17120 SIG: 9
Disconnected from the target VM, address: 'localhost:35735', transport: 'socket'

My query, which points to an action endpoint in our backend through Hasura:

query GetParentCallLogs($sid: String!) {
    parent_call_logs(sid: $sid) {
        calls
    }
}
martinbonnin commented 2 years ago

Hi 👋 . Thanks for reaching out! Do you mind sharing the json response and schema? Either here or to martin@apollographql.com ?

adam-weissert commented 2 years ago

Thanks for the fast response!

Here are the parts of the schema that pertain to this specific endpoint:

          {
            "name": "parent_call_logs",
            "description": null,
            "args": [
              {
                "name": "sid",
                "description": null,
                "type": {
                  "kind": "NON_NULL",
                  "name": null,
                  "ofType": {
                    "kind": "SCALAR",
                    "name": "String",
                    "ofType": null
                  }
                },
                "defaultValue": null
              }
            ],
            "type": {
              "kind": "OBJECT",
              "name": "ParentCallLogsOutput",
              "ofType": null
            },
            "isDeprecated": false,
            "deprecationReason": null
          },
      {
        "kind": "OBJECT",
        "name": "ParentCallLogsOutput",
        "description": null,
        "fields": [
          {
            "name": "calls",
            "description": null,
            "args": [],
            "type": {
              "kind": "NON_NULL",
              "name": null,
              "ofType": {
                "kind": "LIST",
                "name": null,
                "ofType": {
                  "kind": "SCALAR",
                  "name": "json",
                  "ofType": null
                }
              }
            },
            "isDeprecated": false,
            "deprecationReason": null
          }
        ],
        "inputFields": null,
        "interfaces": [],
        "enumValues": null,
        "possibleTypes": null
      },

And this is an example of what the JSON response should be from our backend:

{
  "account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "annotation": "billingreferencetag",
  "answered_by": "machine_start",
  "api_version": "2010-04-01",
  "caller_name": "callerid",
  "date_created": "Fri, 18 Oct 2019 17:00:00 +0000",
  "date_updated": "Fri, 18 Oct 2019 17:01:00 +0000",
  "direction": "outbound-api",
  "duration": "4",
  "end_time": "Fri, 18 Oct 2019 17:03:00 +0000",
  "forwarded_from": "calledvia",
  "from": "+13051416799",
  "from_formatted": "(305) 141-6799",
  "group_sid": "GPXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "parent_call_sid": "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "phone_number_sid": "PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "price": "-0.200",
  "price_unit": "USD",
  "sid": "CA42ed11f93dc08b952027ffbc406d0868",
  "start_time": "Fri, 18 Oct 2019 17:02:00 +0000",
  "status": "completed",
  "subresource_uris": {
    "notifications": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Notifications.json",
    "recordings": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Recordings.json",
    "feedback": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Feedback.json",
    "feedback_summaries": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/FeedbackSummary.json",
    "payments": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Payments.json",
    "events": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Events.json",
    "siprec": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Siprec.json"
  },
  "to": "+13051913581",
  "to_formatted": "(305) 191-3581",
  "trunk_sid": "TKXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json",
  "queue_time": "1000"
}
martinbonnin commented 2 years ago

This response is for a single call, right? Would you have the full response? Starting with "data"?

martinbonnin commented 2 years ago

Also looks like you have a custom json scalar so it'd be useful to know if you have a custom type adapter registered

adam-weissert commented 2 years ago

My apologies. This was related to a clash of the TypeOutput of our Hasura Action and what the endpoint actually returned. Our action has a custom json scalar to return [json]! when really the endpoint did not return an array of json objects, it returned a single json object.

Having tested this now several times it seems this is definitely what was causing the java.util.LinkedHashMap cannot be cast to java.util.List error. I appreciate your assistance on the matter!

Our action in Hasura for future seekers

type Query {
  parent_call_logs(
    sid: String!
  ): ParentCallLogsOutput
}

type ParentCallLogsOutput {
  calls: jsonb! // originally [json]!
}
martinbonnin commented 2 years ago

Nice! Thanks for the follow up!