Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to create @Body converter for java.util.Map<java.lang.String, java.lang.Object> when using kotlinx.serialization Converter #4147

Closed
Goooler opened this issue May 21, 2024 · 1 comment

Comments

@Goooler
Copy link
Contributor

Goooler commented May 21, 2024

interface NetworkApi {
  suspend fun examplePost(@Body params: Map<String, @JvmSuppressWildcards Any>): Unit

  companion object {
    private val retrofit = Retrofit.Builder()
      .baseUrl("https://example.com/")
      .addConverterFactory(
          Json.asConverterFactory(
              "application/json; charset=UTF8".toMediaType(),
          ),
      )
      .build()

    private val api = retrofit.create(NetworkApi::class.java)
    
    suspend fun examplePost() {
      val params = mapOf(
        "name" to "Tom",
        "age" to 20,
      )
      api.examplePost(params)
    }
  }
}
@Goooler
Copy link
Contributor Author

Goooler commented May 24, 2024

This is the issue from Kotlin/kotlinx.serialization#746, workaround:

interface NetworkApi {
  ...

  companion object {
    /**
     * Workaround from https://github.com/Kotlin/kotlinx.serialization/issues/746#issuecomment-779549456.
     */
    private object AnySerializer : KSerializer<Any> {
      override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Any")

      override fun serialize(encoder: Encoder, value: Any) {
        val jsonEncoder = encoder as JsonEncoder
        val jsonElement = serializeAny(value)
        jsonEncoder.encodeJsonElement(jsonElement)
      }

      private fun serializeAny(value: Any?): JsonElement = when (value) {
        is Map<*, *> -> {
          val mapContents = value.entries.associate { mapEntry ->
            mapEntry.key.toString() to serializeAny(mapEntry.value)
          }
          JsonObject(mapContents)
        }
        is List<*> -> {
          val arrayContents = value.map { listEntry -> serializeAny(listEntry) }
          JsonArray(arrayContents)
        }
        is Number -> JsonPrimitive(value)
        is Boolean -> JsonPrimitive(value)
        else -> JsonPrimitive(value.toString())
      }

      override fun deserialize(decoder: Decoder): Any {
        val jsonDecoder = decoder as JsonDecoder
        val element = jsonDecoder.decodeJsonElement()

        return deserializeJsonElement(element)
      }

      private fun deserializeJsonElement(element: JsonElement): Any = when (element) {
        is JsonObject -> {
          element.mapValues { deserializeJsonElement(it.value) }
        }
        is JsonArray -> {
          element.map { deserializeJsonElement(it) }
        }
        is JsonPrimitive -> element.toString()
      }
    }

    private val json = Json {
      serializersModule = SerializersModule {
        contextual(AnySerializer)
      }
    }

    private val retrofit = Retrofit.Builder()
      .baseUrl("https://example.com/")
      .addConverterFactory(
        json.asConverterFactory(
          "application/json; charset=UTF8".toMediaType(),
        ),
      )
      .build()

      ...
  }
}

@Goooler Goooler closed this as not planned Won't fix, can't repro, duplicate, stale May 24, 2024
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

No branches or pull requests

1 participant