You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用StringBuilder与URLEncoder时出现URL编码异常如何解决?

问题:URLEncoder编码URL内容时的空格转义与二次编码问题

以下是我的Kotlin代码:

override suspend fun getData(ids: String): SomeData {
    val itemIds = ids.split(",").iterator()
    val sb = StringBuilder()
    sb.append("/v1/data?ids=")
    itemIds.forEach {
        val assetsJson = myObjectMapperWrapper.getObjectMapper().writeValueAsString(
            DataRequest(type = "Picture", id = it)
        )
        sb.append(URLEncoder.encode(assetsJson, Charsets.UTF_8))
        if (itemIds.hasNext()) sb.append("&data=")
    }
    val uri = sb.toString()
}

其中assetsJson示例为:{"type":"Picture","id":" 12345"}(id字段的空格是故意保留的,API要求如此)。

通过在线工具编码该字符串得到:

%7B%22type%22%3A%22Picture%22%2C%22id%22%3A%22%20%20%20%20%20%20%20%20%2012345%22%7D

将其粘贴到Insomnia中调用API可正常工作。

但本地编码结果为:

%7B%22type%22%3A%22Picture%22%2C%22id%22%3A%22+++++++++12345%22%7D

空格被编码为+而非%20。更严重的是,实际发送到服务器的内容出现二次编码:

%257B%2522type%2522%253A%2522Picture%2522%252C%2522id%2522%253A%2522+++++++++12345%2522%257D

%被转为%25,导致API解析失败,返回错误:

Failed to convert value of type 'java.lang.String[]' to required type 'java.util.Set'; nested exception is java.lang.IllegalArgumentException: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('%' (code 37)): expected a valid value (JSON String, Number, Array, Object or token 'null', 'true' or 'false') at [Source: (String)"%7B%22type%22%3A%22Picture%22%2C%22id%22%3A%22 12345%22%7D"; line: 1, column: 2]


解决方法

1. 避免手动拼接URL参数,改用HTTP客户端的参数构建API

二次编码的核心原因是:手动用URLEncoder.encode编码后,后续的HTTP客户端(比如OkHttp、Retrofit)会自动对URL查询参数进行编码,导致重复转义。

正确的做法是让客户端负责参数编码,只需传入原始的JSON字符串即可。以OkHttp为例:

override suspend fun getData(ids: String): SomeData {
    val itemIds = ids.split(",")
    val urlBuilder = HttpUrl.Builder()
        .scheme("https") // 根据实际情况修改
        .host("your-api-host.com") // 根据实际情况修改
        .addPathSegments("v1/data")
    
    itemIds.forEachIndexed { index, id ->
        val assetsJson = myObjectMapperWrapper.getObjectMapper().writeValueAsString(
            DataRequest(type = "Picture", id = id)
        )
        // 第一个参数用ids,后续用data
        val paramKey = if (index == 0) "ids" else "data"
        urlBuilder.addQueryParameter(paramKey, assetsJson)
    }
    
    val url = urlBuilder.build()
    // 后续用url发起请求
}

HttpUrl.Builder会自动按照URL标准编码参数,空格会被转为%20,且不会出现二次编码问题。

如果用Retrofit,可以通过@Query注解传递参数,框架会自动处理编码:

interface DataApi {
    @GET("/v1/data")
    suspend fun getData(
        @Query("ids") firstIdJson: String,
        @Query("data") otherIdJsons: List<String>
    ): SomeData
}

// 调用时
val itemIds = ids.split(",")
val firstJson = myObjectMapperWrapper.getObjectMapper().writeValueAsString(
    DataRequest(type = "Picture", id = itemIds.first())
)
val otherJsons = itemIds.drop(1).map {
    myObjectMapperWrapper.getObjectMapper().writeValueAsString(
        DataRequest(type = "Picture", id = it)
    )
}
val result = dataApi.getData(firstJson, otherJsons)

2. 若必须手动编码,处理空格并避免二次编码

如果因特殊原因必须手动拼接URL,需要:

  • 将URLEncoder.encode的结果中的+替换为%20,符合URL标准编码
  • 确保后续客户端不会再次编码(比如设置客户端禁用自动编码,或确认直接使用拼接好的完整URL发起请求)

修改后的手动拼接代码:

override suspend fun getData(ids: String): SomeData {
    val itemIds = ids.split(",").iterator()
    val sb = StringBuilder()
    sb.append("/v1/data?ids=")
    itemIds.forEach {
        val assetsJson = myObjectMapperWrapper.getObjectMapper().writeValueAsString(
            DataRequest(type = "Picture", id = it)
        )
        // 编码后替换+为%20
        val encodedJson = URLEncoder.encode(assetsJson, Charsets.UTF_8).replace("+", "%20")
        sb.append(encodedJson)
        if (itemIds.hasNext()) sb.append("&data=")
    }
    val uri = sb.toString()
    // 确保后续请求时直接使用该uri,不进行额外编码
}

这种方式风险较高,若后续客户端仍会自动编码,还是会出现二次转义,因此优先推荐第一种方法。


内容的提问来源于stack exchange,提问作者hc0re

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 16:55:27