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.74k stars 653 forks source link

IllegalStateException when writing coordinates to JSON #5010

Closed Orbyt closed 1 year ago

Orbyt commented 1 year ago

Version

3.7.4

Summary

IllegalStateException occurs when executing query after migration to Apollo 3.

See log section for stacktrace.

The piece of input referenced ({"location":{"latitude":31.9496748,"longitude":-108.8067046}}) is definately valid JSON. The error gives no indication as to why that input cannot be written to JSON. The input code for the query has not really changed, with the exception of changes related to the use of Optionals as required by Apollo 3. The stack trace seems to reference generated *_InputAdapter classes in the project.

What might be the issue?

Steps to reproduce the behavior

N/A

Logs

java.lang.IllegalStateException: Cannot write {"location":{"latitude":31.9496748,"longitude":-108.8067046}} to Json
    at com.apollographql.apollo3.api.json.-JsonWriters.writeAny(JsonWriters.kt:40)
    at com.apollographql.apollo3.api.Adapters$AnyAdapter$1.toJson(Adapters.kt:203)
    at com.apollographql.apollo3.api.Adapters$AnyAdapter$1.toJson(Adapters.kt:211)
    at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:65)
    at com.apollographql.apollo3.api.PresentAdapter.toJson(Adapters.kt:92)
    at com.example.example.type.adapter.ProductRecommendationQuestionInput_InputAdapter.toJson(ProductRecommendationQuestionInput_InputAdapter.kt:37)
    at com.example.example.type.adapter.ProductRecommendationQuestionInput_InputAdapter.toJson(ProductRecommendationQuestionInput_InputAdapter.kt:21)
    at com.apollographql.apollo3.api.ObjectAdapter.toJson(Adapters.kt:313)
    at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:65)
    at com.apollographql.apollo3.api.ListAdapter.toJson(Adapters.kt:39)
    at com.apollographql.apollo3.api.ListAdapter.toJson(Adapters.kt:25)
    at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:65)
    at com.example.example.adapter.ProductRecommendationQuery_VariablesAdapter.toJson(ProductRecommendationQuery_VariablesAdapter.kt:30)
    at com.example.example.ProductRecommendationQuery.serializeVariables(ProductRecommendationQuery.kt:40)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.composePostParams(DefaultHttpRequestComposer.kt:116)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.access$composePostParams(DefaultHttpRequestComposer.kt:68)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.buildPostBody(DefaultHttpRequestComposer.kt:215)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer.compose(DefaultHttpRequestComposer.kt:62)
    at com.apollographql.apollo3.network.http.HttpNetworkTransport.execute(HttpNetworkTransport.kt:43)
    at com.apollographql.apollo3.interceptor.NetworkInterceptor.intercept(NetworkInterceptor.kt:22)
    at com.apollographql.apollo3.interceptor.DefaultInterceptorChain.proceed(ApolloInterceptor.kt:23)
    at com.apollographql.apollo3.cache.http.HttpCache$httpCache$2.intercept(HttpCacheExtensions.kt:106)
    at com.apollographql.apollo3.interceptor.DefaultInterceptorChain.proceed(ApolloInterceptor.kt:23)
    at com.apollographql.apollo3.ApolloClient.executeAsFlow(ApolloClient.kt:178)
    at com.apollographql.apollo3.ApolloCall.toFlow(ApolloCall.kt:97)
    at com.apollographql.apollo3.ApolloCall.execute(ApolloCall.kt:106)
    at com.example.example.data.remote.RemoteRepoImpl.getProductRecommendation(RemoteRepoImpl.kt:1333)
martinbonnin commented 1 year ago

Hi 👋 Thanks for sending this!

Do you know what is the runtime type of {"location":{"latitude":31.9496748,"longitude":-108.8067046}}. It looks like it's a custom scalar mapped to Any (default behaviour). In that case, apollo3 supports Map, List, String, Boolean, Int, Double, JsonNumber and Long. Anything else will throw the error above (even if .toString() is valid json)

Orbyt commented 1 year ago

@martinbonnin I believe it might actually be a JSONObject, which is rather ironic. I will need to verify the type using a debugger.

I will post some additional data from the debugger.

Orbyt commented 1 year ago

Ok, for context, here's the actual query:

override suspend fun getProductRecommendation(questionInputList: List<ProductRecommendationQuestionInput>): Result<ProductRecommendationQuery.ProductRecommendation> {

    val query = ProductRecommendationQuery(questionInputList)
    try {
        // IllegalStateException occurs here.
        val response = client.get().query(query).execute()
        if (response.hasErrors() || response.data?.productRecommendation == null) {
            // ...
        } else {
            // Success
            // ...
        }
    } catch (e: ApolloException) {
        // ...
    }
}

...and here's the debugger output of the questionInputList that is used for the query's input:

Screen Shot 2023-06-07 at 8 58 04 AM

The first object in that list contains the coordinates:

ProductRecommendationQuestionInput(answer=ProductRecommendationAnswerInput(embedded=com.apollographql.apollo3.api.Optional$Absent@a0d3523, input=Present(value=Obstetrician and Gynecologist (OBGYN)), value=Present(value={"medicalTerm":{"uid":"7abab5ee-d2d0-4fca-a33c-155a8d5ea121","type":"physician"}})), params=Present(value={"location":{"latitude":33.1796743,"longitude":-103.3958121}}), question=need)

Here's the type according to the debugger:

Screen Shot 2023-06-07 at 9 01 14 AM
martinbonnin commented 1 year ago

Edit: looks like what you need is use strings (see comment below) Edit2: well maybe not after all :)

~Gotcha. That's interesting I wonder how come this worked in version 2. In all cases, you should be able to convert to a Map with something like:~

fun Any.convert(): Any {
  return when (this) {
    is JSONObject -> this.keys().toList().map {
      it to this.get(it).convert()
    }.toMap()
    is JSONArray -> this.toList().map { it.convert() }
    is Long, is Int, is String, is Boolean, null -> this
    else -> error("Cannot convert $this of class ${this.javaClass} to JSON")
  }
}

~and then call~

jsonObject.convert()
martinbonnin commented 1 year ago

Looking at the 2.x code, it was converting everything to a String. So your JSONObject was actually sent as a "String" to your server. If you want to keep the same behaviour, call .toString() at the call site:

val query = ProductRecommendationQuery(
  listOf(
    ProductRecommendationQuestionInput(
      params = Optional.Present(jsonObject.toString())
    )
  )
)

or define your own adapter:

object adapter: Adapter<JSONObject> {
  override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): JSONObject {
    return JSONObject(reader.nextString())
  }

  override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: JSONObject) {
    writer.value(value.toString())
  }
}
martinbonnin commented 1 year ago

the input type as defined by the query is a List<ProductRecommendationQuestionInput?>?

Indeed, I just edited the comment above. I think something like so would work:

val query = ProductRecommendationQuery(
  listOf(
    ProductRecommendationQuestionInput(
      params = Optional.Present(jsonObject.toString())
    )
  )
)

The important part is that the JSONObject is now a String, everything else can stay the same.

For some background there, this is a trade off between leniency and correctness. The 2.x behaviour was more lenient but also is taking the risk of hiding errors when that behaviour is not the expected one. At least the exception forces the caller to look into the issue even if in your case it means some extra care, sorry for that.

Orbyt commented 1 year ago

@martinbonnin Ok, well I just tried forcing the JSONObject to a String via a rather hacky solution:

override suspend fun getProductRecommendation(questionInputList: List<ProductRecommendationQuestionInput>): Result<ProductRecommendationQuery.ProductRecommendation> {

    val modifiedInput = mutableListOf<ProductRecommendationQuestionInput>()
     questionInputList.forEachIndexed { index, productRecommendationQuestionInput ->
        // If the params contain a JSONObject, convert it to a String.
        if (productRecommendationQuestionInput.params.getOrNull() != null &&
            productRecommendationQuestionInput.params.getOrNull() is JSONObject) {
            val newInput = productRecommendationQuestionInput.copy(
                params = Optional.present((productRecommendationQuestionInput.params.getOrNull() as JSONObject).toString()))
            modifiedInput.add(newInput)
        } else {
            modifiedInput.add(productRecommendationQuestionInput)
        }
    }
    val query = ProductRecommendationQuery(modifiedInput)
    try {
        val response = client.get().query(query).execute()
        // ...
    }
}

The IllegalStateException doesn't occur, but the query fails with the following error:

Variable questions of type [ProductRecommendationQuestionInput] was provided invalid value for 0.params (Could not coerce value "{\"location\":{\"latitude\":31.9496748,\"longitude\":-108.8067046}}" to AnyHash)

I did find this piece of code in the application...perhaps it's related to the issue:

class AnyHashAdapter : CustomTypeAdapter<Any> {
    override fun encode(value: Any): CustomTypeValue<*> {
        return when (value) {
            is JSONObject -> CustomTypeValue.GraphQLJsonObject(jsonObjectToMap(value))
            else -> CustomTypeValue.GraphQLString(value.toString())
        }
    }

    override fun decode(value: CustomTypeValue<*>): Any {
        return when(value) {
            is CustomTypeValue.GraphQLJsonObject -> JSONObject(value.value)
            else -> value.value ?: EMPTY
        }
    }
}

To be candid, I am not entirely sure what that code is doing, or why a String (the thing converted from a JSONObject) would be sent through that adapter.

martinbonnin commented 1 year ago

Thanks for sending this. Turns our you had some specific handling to convert your JSONObject to a Map (jsonObjectToMap(value)). The 3.x equivalent would be something like so:

class AnyHashAdapter : Adapter<Any> {
  override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Any {
    return when(val value = reader.readAny()) {
      is Map<*,*> -> JSONObject(value)
      else -> value.toString()
    }
  }

  override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: Any) {
    when (value) {
      is JSONObject -> writer.writeAny(jsonObjectToMap(value))
      else -> writer.value(value.toString())
    }
  }
}
Orbyt commented 1 year ago

@martinbonnin Yes, but then why would the issue have occured in the first place if the JSONObject is/was being converted to a Map (considering that Map is a supported type as mentioned earlier)?

Must it be converted to a String (as done using the rather hacky solution I posted), and only then is it sent through an adapter?

martinbonnin commented 1 year ago

Yes, but then why would the issue have occured in the first place if the JSONObject is/was being converted to a Map

It's hard to tell without more details. Can you share the code that triggered the issue?

Must it be converted to a String

I initially though so but turns out you had special handling to convert to a Map so Map is most likely what you want

Orbyt commented 1 year ago

@martinbonnin The query, query inputs and relevant adapter were posted. Is there a particular piece of code that would provide more details? I am unsure as to what else is relevant.

The query takes inputs that I believe originate from a server, so the whole thing is more or less dynamic.

The original error (Cannot write {"location":{"latitude":31.9496748,"longitude":-108.8067046}} to Json) seemed to suggest that a JSONObject could not be converted to Json, which is rather bizarre. Regardless, the first error was occuring while the relevant object was still a JSONObject. After converting it to a String as suggested above, it then seems to get put through adapters, which seemed to cause the second error.

I am a little unsure as to why the Apollo SDK isn't simply able to handle a JSONObject when attempting to write JSON, and why the input object seems to only get to adapters if converted to a String.

The adapter that I posted was surely created to handle Apollo 2 edge cases, so if it's not needed or been deprecated please let me know.

I certainly appreciate your help with this matter. Thank you!

martinbonnin commented 1 year ago

The query, query inputs and relevant adapter were already posted. Is there a particular piece of code that would provide more details? I am unsure as to what else is relevant.

Gotcha. Sorry I was confused getting all the pieces together. More comments below

why the Apollo SDK isn't simply able to handle a JSONObject when attempting to write JSON

JSONObject is coming from an external dependency of your project like typically org.json. Apollo Kotlin knows nothing about org.json so it can't deal with it. Apollo Koltin cannot support all JSON libraries out there like org.json but also Gson, Jackson, moshi, etc.. It only knows about Kotlin StdLib. This is why you had AnyHashAdapter in the 2.x codebase. AnyHashAdapter converts from org.json.JSONObject (that Apollo Kotlin doesn't know about) to kotlin.collections.Map (that Apollo Kotlin knows about because it's in the Kotlin standard library)

why the input object seems to only get to adapters if converted to a String.

Apologies for steering this discussion to Strings, this was a red herring. I missed that you previously had a CustomTypeAdapter. This is the part that needs to be ported to Apollo Kotlin 3

The adapter that I posted was surely created to handle Apollo 2 edge cases, so if it's not needed or been deprecated please let me know.

It's still needed. The adapter converts (adapts) from JSONObject in your project to Map (in Apollo Kotlin). It was named CustomTypeAdapter in Apollo 2 and it's now named just Adapter. I believe something like below should help?

class AnyHashAdapter : Adapter<Any> {
  override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: Any) {
    when (value) {
      // The jsonObejctToMap here is the important part and will convert from JSONObject to Map
      is JSONObject -> writer.writeAny(jsonObjectToMap(value))
      else -> writer.value(value.toString())
    }
  }  

  override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Any {
    return when(val value = reader.readAny()) {
      is Map<*,*> -> JSONObject(value)
      else -> value.toString()
    }
  }
}

I certainly appreciate your help with this matter. Thank you!

Same, thanks for posting all the details! I hope the above makes a bit more sense? Let me know otherwise!

Orbyt commented 1 year ago

@martinbonnin I attempted to implement your suggested adapter solution, but the same issue persisted. Actually, as far as I can tell, that adapter isn't even reached before the intial error occurs (I sort of commented on that earlier).

I looked some more at the query inputs and there are other objects in that query input that are JSONObjects and seem to be handled just fine. Here's a photo of the debugger..note the JSONObjects that are not the coordinates one:

screeny3

Curiously, that seemed to work, and the query went through. So, the issue doesn't seem to be with the JSONObject type, as there are other JSONObjects in that query input.

Do you have any idea why the issue would occur, specifically, with that one JSONObject representing coordinates?

martinbonnin commented 1 year ago

That's interesting. Maybe the different input fields are of different types? Can you share your schema? If they are of different types, you'll need to register the adapter for each custom GraphQL scalar that you want to handle (See https://www.apollographql.com/docs/kotlin/essentials/custom-scalars/).

Orbyt commented 1 year ago

@martinbonnin The types should be shown in that screenshot. Using the example shown in the screenshot modifiedInput.get(0).answer.value is an Optional<Any?>, and so is modifiedInput.get(0).params (again, that's the original coordinates object). Here's the generated data classes, which should hopefully be equivalent to posting the schema:

public data class ProductRecommendationQuery(
  public val questions: List<ProductRecommendationQuestionInput?>?,
) : Query<ProductRecommendationQuery.Data> {
public data class ProductRecommendationQuestionInput(
  public val answer: ProductRecommendationAnswerInput,
  public val params: Optional<Any?> = Optional.Absent,
  public val question: String,
)
public data class ProductRecommendationAnswerInput(
  public val embedded: Optional<Any?> = Optional.Absent,
  public val input: Optional<String?> = Optional.Absent,
  public val `value`: Optional<Any?> = Optional.Absent,
)

So both questions[0].params and questions[0].answer.value are of type Optional<Any?>. Note that I also attempted to make the "offending" object ({"location":{"latitude":31.9496748,"longitude":-108.8067046}}) use Strings instead of Doubles, though that did not seem to make a difference. Is there any reason Apollo would error when attempting to parse that particular object, perhaps because of the numbers? Perhaps it is attempting to convert the numbers into some type and failing?

martinbonnin commented 1 year ago

Sorry I meant the GraphQL types in your schema. Each GraphQL type is mapped to a Kotlin type depending your Gradle configuration (see https://www.apollographql.com/docs/kotlin/essentials/custom-scalars/). Then depending the adapters you register, they will be called to "adapt" these types. So in order to troubleshoot all of this, you'll need to look at your schema, Gradle configuration and registered adapters. Sorry I know that's a lot of things to look at but there's no real way around this. If you can upload a reproducer somewhere that might be easier.

martinbonnin commented 1 year ago

Hey @Orbyt did you manage to make it work? Did you see anything in your schema?

Orbyt commented 1 year ago

@martinbonnin These should be the relevant types from the schema.graphqls file:

"""
Input type for product recommendation answers.
                     Accepts a value of any type and an input string.
"""
input ProductRecommendationAnswerInput {
  embedded: AnyHash

  input: String

  value: AnyScalar
}

"""
Object type for product recommendation question answers.
"""
type ProductRecommendationAnswerType {
  embedded: AnyHash

  input: String

  value: AnyScalar
}

"""
Input type for product recommendation questions.
                     Accepts an answer, a question type, and hash of additional
                     parameters describing the question.
"""
input ProductRecommendationQuestionInput {
  answer: ProductRecommendationAnswerInput!

  params: AnyHash

  question: String!
}

"""
Object type for product recommendations.
"""
type ProductRecommendationType {
  conclusion(saveLocation: Boolean): ProductRecommendationConclusionType

  questions: [ProductRecommendationQuestionType]!
}

productRecommendation(questions: [ProductRecommendationQuestionInput]): ProductRecommendationType

Adapters seem to be registered in two places:

fun provideApolloClient(spHelper: SPHelper, @Named("jarvis") okHttpClient: Provider<OkHttpClient>, context: Context) = ApolloClient.Builder()
            .serverUrl(spHelper.getSelectedGRUrl().replace("app", "api") + GRAPHQL)
            .httpCache(File(context.cacheDir,"apolloCache"), 1024 * 1024)
            .okHttpClient(okHttpClient.get())
            .addCustomScalarAdapter(AnyHash.type, HashAdapter())
            .addCustomTypeAdapter(Date.type, DateTypeAdapter())
            .addCustomTypeAdapter(UUID.type, UUIDAdapter())
            .addCustomTypeAdapter(AnyScalar.type, AnyScalarAdapter())
            .build()

build.gradle

apollo {
    mapScalar("UuidV4", "kotlin.String")
    mapScalar("DateType", "kotlin.String")
    mapScalar("TimeOfDay", "kotlin.String")
    mapScalar("AnyScalar", "kotlin.Any")
    mapScalar("TermUidScalar", "kotlin.String")
    mapScalar(
            "Hash",
            "com.example.example.data.remote.adapters.ClaimsAddress",
            "com.example.example.data.remote.adapters.HashAdapter"
    )

    srcDir(file("src/main/graphql/com/example/example/"))

    packageName.set("com.example.example")
    generateOptionalOperationVariables.set(false)
}

I am still investigating the issue. Please let me know if the code posted here gives you an idea of what may be going wrong, or if the schema types posted were not what you were referring to.

martinbonnin commented 1 year ago

Thanks! That helps. Looks like you're not mapping AnyHash like the other scalars. Can you try the following?

apollo {
    // Keep these ones
    mapScalar("UuidV4", "kotlin.String")
    mapScalar("DateType", "kotlin.String")
    mapScalar("TimeOfDay", "kotlin.String")
    mapScalar("AnyScalar", "kotlin.Any")
    mapScalar("TermUidScalar", "kotlin.String")
    mapScalar(
            "Hash",
            "com.example.example.data.remote.adapters.ClaimsAddress",
            "com.example.example.data.remote.adapters.HashAdapter"
    )

   // Add AnyHash
   mapScalar("AnyHash", "kotlin.Any")

   // keep the rest ..
Orbyt commented 1 year ago

@martinbonnin Isn't that already handled via the registered adapter posted above? Here's the adapters registered at runtime:

fun provideApolloClient(spHelper: SPHelper, @Named("jarvis") okHttpClient: Provider<OkHttpClient>, context: Context) = ApolloClient.Builder()
            .serverUrl(spHelper.getSelectedGRUrl().replace("app", "api") + GRAPHQL)
            .httpCache(File(context.cacheDir,"apolloCache"), 1024 * 1024)
            .okHttpClient(okHttpClient.get())
            .addCustomScalarAdapter(AnyHash.type, HashAdapter())
            .addCustomTypeAdapter(Date.type, DateTypeAdapter())
            .addCustomTypeAdapter(UUID.type, UUIDAdapter())
            .addCustomTypeAdapter(AnyScalar.type, AnyScalarAdapter())
            .build()

I did try adding mapScalar("AnyHash", "kotlin.Any") to the build.gradle file but that resulted in another issue:

java.lang.ClassCastException: org.json.JSONObject cannot be cast to java.util.Map
    at com.example.example.data.remote.adapters.HashAdapter.toJson(HashAdapter.kt:14)
    at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:65)
    at com.apollographql.apollo3.api.PresentAdapter.toJson(Adapters.kt:92)
    at com.example.example.type.adapter.ProductRecommendationQuestionInput_InputAdapter.toJson(ProductRecommendationQuestionInput_InputAdapter.kt:39)
    at com.example.example.type.adapter.ProductRecommendationQuestionInput_InputAdapter.toJson(ProductRecommendationQuestionInput_InputAdapter.kt:23)
    at com.apollographql.apollo3.api.ObjectAdapter.toJson(Adapters.kt:313)
    at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:65)
    at com.apollographql.apollo3.api.ListAdapter.toJson(Adapters.kt:39)
    at com.apollographql.apollo3.api.ListAdapter.toJson(Adapters.kt:25)
    at com.apollographql.apollo3.api.NullableAdapter.toJson(Adapters.kt:65)
    at com.example.example.adapter.ProductRecommendationQuery_VariablesAdapter.toJson(ProductRecommendationQuery_VariablesAdapter.kt:30)
    at com.example.example.ProductRecommendationQuery.serializeVariables(ProductRecommendationQuery.kt:40)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.composePostParams(DefaultHttpRequestComposer.kt:116)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.access$composePostParams(DefaultHttpRequestComposer.kt:68)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer$Companion.buildPostBody(DefaultHttpRequestComposer.kt:215)
    at com.apollographql.apollo3.api.http.DefaultHttpRequestComposer.compose(DefaultHttpRequestComposer.kt:62)
    at com.apollographql.apollo3.network.http.HttpNetworkTransport.execute(HttpNetworkTransport.kt:43)
    at com.apollographql.apollo3.interceptor.NetworkInterceptor.intercept(NetworkInterceptor.kt:22)
    at com.apollographql.apollo3.interceptor.DefaultInterceptorChain.proceed(ApolloInterceptor.kt:23)
    at com.apollographql.apollo3.cache.http.HttpCache$httpCache$2.intercept(HttpCacheExtensions.kt:106)
    at com.apollographql.apollo3.interceptor.DefaultInterceptorChain.proceed(ApolloInterceptor.kt:23)
    at com.apollographql.apollo3.ApolloClient.executeAsFlow(ApolloClient.kt:178)
    at com.apollographql.apollo3.ApolloCall.toFlow(ApolloCall.kt:97)
    at com.apollographql.apollo3.ApolloCall.execute(ApolloCall.kt:106)
    at com.example.example.data.remote.RemoteRepoImpl.getProductRecommendation(RemoteRepoImpl.kt:1359)

I'm unsure as to whether that is a new issue brought about by the addition of registering the AnyHash type in the build.gradle file, or if that fixed the issue and this is something else.

martinbonnin commented 1 year ago

Isn't that already handled via the registered adapter posted above? Here's the adapters registered at runtime:

Not really. By default the codegen will always use AnyAdapter for all custom scalars that are not mapped in the Gradle file. If you want to provide an adapter at runtime, you need to map it.

java.lang.ClassCastException: org.json.JSONObject cannot be cast to java.util.Map
    at com.example.example.data.remote.adapters.HashAdapter.toJson(HashAdapter.kt:14)

Sounds like this is progress. Can you share your implementation of HashAdapter?

martinbonnin commented 1 year ago

Hi @Orbyt did you end up finding a solution to this issue? Anything else we can help with here?

martinbonnin commented 1 year ago

I'm going to assume you found a solution. Feel free to comment and we will reopen.