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

iOS POST请求JSON解析错误(NSCocoaErrorDomain Code=3840)修复咨询

分析并修复POST请求的JSON解析错误

错误原因

NSCocoaErrorDomain Code=3840 错误本质是服务器返回的内容不是标准JSON格式,结合你的代码和Postman请求场景,核心问题有两个:

  1. 请求格式不匹配:你用JSON格式发送带图片的请求,但服务器实际要求multipart/form-data(文件上传的标准格式),导致服务器返回非JSON错误响应
  2. 数据类型错误:rate下的availability/beauty/purity以及amount应该是数值类型,但你传递了字符串,触发服务器验证失败,返回非JSON提示

修复方案

1. 改用MultipartFormData发送请求

因为请求包含图片文件,必须使用multipart/form-data格式,而非JSON。修改后的完整代码如下:

@objc func postActionButton() {
    // 检查AccessToken
    guard let accessToken = UserDefaults.standard.string(forKey: "AccessToken") else {
        print("Access token is missing")
        return
    }
    
    // 验证请求URL
    guard let url = URL(string: "\(apiLink)/report/create_report/") else {
        print("Invalid URL")
        return
    }
    
    // 检查选中的图片
    guard let image = selectedImage else {
        print("No image selected")
        return
    }
    guard let imageData = image.jpegData(compressionQuality: 0.8) else {
        print("Failed to convert image to data")
        return
    }
    
    // 构建基础请求
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
    
    // 配置MultipartFormData边界
    let boundary = UUID().uuidString
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
    var body = Data()
    
    // 添加普通文本字段
    // description
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"description\"\r\n\r\n".data(using: .utf8)!)
    body.append((descriptionTextView.text ?? "").data(using: .utf8)!)
    body.append("\r\n".data(using: .utf8)!)
    
    // report_status(固定值)
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"report_status\"\r\n\r\n".data(using: .utf8)!)
    body.append("Accepted".data(using: .utf8)!)
    body.append("\r\n".data(using: .utf8)!)
    
    // type_obj
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"type_obj\"\r\n\r\n".data(using: .utf8)!)
    body.append((selectedOption ?? "").data(using: .utf8)!)
    body.append("\r\n".data(using: .utf8)!)
    
    // id_obj
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"id_obj\"\r\n\r\n".data(using: .utf8)!)
    body.append("\(selectedOptionID ?? 0)".data(using: .utf8)!)
    body.append("\r\n".data(using: .utf8)!)
    
    // 处理嵌套的rate字段(转成JSON字符串)
    let rateDict: [String: Any] = [
        "availability": Double(rateInputView.transportTextField.text ?? "") ?? 0.0,
        "beauty": Double(rateInputView.beautyTextField.text ?? "") ?? 0.0,
        "purity": Double(rateInputView.pollutionTextField.text ?? "") ?? 0.0
    ]
    if let rateData = try? JSONSerialization.data(withJSONObject: rateDict),
       let rateString = String(data: rateData, encoding: .utf8) {
        body.append("--\(boundary)\r\n".data(using: .utf8)!)
        body.append("Content-Disposition: form-data; name=\"rate\"\r\n\r\n".data(using: .utf8)!)
        body.append(rateString.data(using: .utf8)!)
        body.append("\r\n".data(using: .utf8)!)
    }
    
    // 处理嵌套的Results数组(转成JSON字符串)
    let wasteItem: [String: Any] = [
        "waste_id": [
            "name": wasteInputView.wasteLabel.text ?? "",
            "unit_of_waste": "kg"
        ],
        "amount": Double(wasteInputView.bulbTextField.text ?? "") ?? 0.0
    ]
    let resultsArray = [wasteItem]
    if let resultsData = try? JSONSerialization.data(withJSONObject: resultsArray),
       let resultsString = String(data: resultsData, encoding: .utf8) {
        body.append("--\(boundary)\r\n".data(using: .utf8)!)
        body.append("Content-Disposition: form-data; name=\"Results\"\r\n\r\n".data(using: .utf8)!)
        body.append(resultsString.data(using: .utf8)!)
        body.append("\r\n".data(using: .utf8)!)
    }
    
    // 添加图片文件
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"photo\"; filename=\"image.jpg\"\r\n".data(using: .utf8)!)
    body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
    body.append(imageData)
    body.append("\r\n".data(using: .utf8)!)
    
    // 结束边界
    body.append("--\(boundary)--\r\n".data(using: .utf8)!)
    
    request.httpBody = body
    
    // 发送请求并处理响应
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            print("Request error: \(error)")
            return
        }
        
        guard let data = data else {
            print("No data received")
            return
        }
        
        // 打印原始响应内容,方便排查非JSON错误
        if let responseString = String(data: data, encoding: .utf8) {
            print("Raw response: \(responseString)")
        }
        
        do {
            if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
                print("Response JSON: \(json)")
            }
        } catch {
            print("Failed to parse JSON: \(error)")
        }
    }
    
    task.resume()
}

2. 关键修改说明

  • 请求格式切换:从JSON改为multipart/form-data,符合文件上传的标准规范,匹配Postman的请求结构
  • 数据类型修正:将rate下的三个评分字段和amount从字符串转为Double类型,匹配服务器要求的数值格式
  • 嵌套结构处理:把rate和Results这类嵌套JSON结构转成字符串后,作为form-data的字段值发送
  • 调试辅助:添加原始响应内容打印,方便排查服务器返回的非JSON错误提示(比如参数验证失败的文本)

3. 额外排查建议

  • 检查selectedOptionID的类型,确保和服务器要求的ID格式(整数/字符串)一致
  • 验证apiLink是否正确,避免因URL拼写错误导致服务器返回404等非JSON响应
  • 如果仍有错误,查看打印的Raw response内容,根据服务器提示调整参数

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 07:17:51