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

同一份JSON的POST请求在Reqbin成功但Kotlin移动端失败求助

移动端Ping数据JSON解析异常排查

问题背景

使用Java/Kotlin开发移动端Ping功能,将Ping数据以JSON格式发送至Azure Web App,后者接收后存入SQL数据库。当前遇到的问题是:移动端发送的JSON payload在Web App端无法解析,Azure日志流抛出Json input formatter异常;但用reqbin.com发送完全相同的JSON时请求成功,数据可正常存入数据库,无法定位问题点。

相关代码与信息

1. 构造JSON payload的Kotlin代码

// 导入必要依赖
fun createJsonPing(
    timeOfPing: LocalDateTime,
    deviceId: String,
    smartphoneId: String,
    note: String
): String {
    val messageObject = JSONObject()
    try {
        messageObject.put("TimeOfPing", formatDateTimeToISO(timeOfPing))
        messageObject.put("SmartphoneId", smartphoneId)
        messageObject.put("DeviceId", deviceId)
        messageObject.put("Note", note)
    } catch (e: Exception) {
        e.printStackTrace()
        throw JSONException(e.message ?: "Unknown error")
        Log.i("createJsonPing", "Failed to construct Json ping")
    }
    val jsonMessageStringPing = messageObject.toString()
    Log.i("createPing", "Ping: \n" + jsonMessageStringPing)
    Log.i("createPing", "Size of ping is: ${jsonMessageStringPing.toByteArray(Charsets.UTF_8).size} b")
    return jsonMessageStringPing
}

fun formatDateTimeToISO(dateTime: LocalDateTime): String {
    val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
    return dateTime.format(formatter)
}

2. Ktor发送请求代码

// 初始化客户端
private val client = HttpClient(CIO) {
    install(Logging) {
        level = LogLevel.ALL
    }
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
            prettyPrint = true
            //isLenient = true
        })
    }
}

suspend fun simpleSend() {
    try {
        val response: HttpResponse = client.post("https://myazurewebapp.azurewebsites.net/ping") {
            headers {
                append(HttpHeaders.Accept, "application/json")
                append(HttpHeaders.ContentType, "application/json")
            }
            //contentType(ContentType.Application.Json) // 也曾尝试使用此行
            setBody(createJsonPing(
                LocalDateTime.now(),
                "TEST",
                "test",
                "testing ping"))
        }.body()

        Log.i(TAG, "HTTP Response: $response")
    } catch (e: Exception) {
        Log.i(TAG, "Request failed: $e")
    } finally {
        close()
    }
}

3. 发送的JSON示例

{"TimeOfPing":"2024-06-21T14:38:00.121",
"SmartphoneId":"TEST",
"DeviceId":"test",
"Note":"testing ping"}

4. Azure日志流关键错误信息

2024-06-21 12:20:50.901 +00:00 [Debug] Microsoft.AspNetCore.Mvc.Formatters.SystemTextJsonInputFormatter: JSON input formatter threw an exception: The JSON value could not be converted to api.Dtos.MessagePing.CreateMessagePingRequestDto. Path: $ | LineNumber: 0 | BytePositionInLine: 127.

5. Web App接收请求的CreateMessagePingRequestDto结构

{
    "type": "object",
    "properties": {
        "TimeOfPing": {
            "type": "string"
        },
        "SmartphoneId": {
            "type": "string"
        },
        "DeviceId": {
            "type": "string"
        },
        "Note": {
            "type": "string"
        }
    },
    "additionalProperties": false
}

排查思路与解决方案

核心问题分析

手动构造JSON字符串并通过Ktor发送时,Ktor的ContentNegotiation组件会将String类型的body当作纯文本处理,而非JSON对象。即使设置了Content-Type: application/json,后端收到的可能是被转义的字符串(例如原JSON被包裹在双引号中,内部引号转义),导致后端JSON解析器无法将其转换为目标DTO。

解决方案:改用数据类自动序列化

  1. 定义对应的数据类
data class PingRequest(
    val TimeOfPing: String,
    val SmartphoneId: String,
    val DeviceId: String,
    val Note: String
)
  1. 修改发送逻辑,直接传入数据类
suspend fun simpleSend() {
    try {
        val pingRequest = PingRequest(
            TimeOfPing = formatDateTimeToISO(LocalDateTime.now()),
            SmartphoneId = "TEST",
            DeviceId = "test",
            Note = "testing ping"
        )
        val response = client.post("https://myazurewebapp.azurewebsites.net/ping") {
            contentType(ContentType.Application.Json)
            setBody(pingRequest) // 直接传入数据类,由ContentNegotiation自动序列化
        }
        Log.i(TAG, "HTTP Response Status: ${response.status}")
    } catch (e: Exception) {
        Log.i(TAG, "Request failed: $e")
    } finally {
        client.close()
    }
}

额外排查点

  • 抓包验证实际请求体:使用Charles或Wireshark抓取移动端发送的请求,确认body是否与日志中打印的JSON一致,是否存在转义或编码问题。
  • 后端日期格式兼容:如果后端TimeOfPing字段是DateTime类型而非字符串,需确保后端JSON解析器支持yyyy-MM-dd'T'HH:mm:ss.SSS格式(可在后端配置JsonSerializerOptions指定日期格式)。
  • 字符编码一致性:确保移动端发送和后端接收均使用UTF-8编码,避免因编码差异导致的解析失败。

内容的提问来源于stack exchange,提问作者koralröv

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:45:24