问题:在Swift中为UrlRequest编码JSON时出现问题。

huangapple go评论67阅读模式
英文:

Problem encoding JSON for a UrlRequest in Swift

问题

我正在尝试设置一个API服务。我有一个简单的登录调用,看起来像这样:

enum HttpMethod: Equatable {
    case get([URLQueryItem])
    case put(Data?)
    case post(Data?)
    
    var name: String {
        switch self {
        case .get: return "GET"
        case .put: return "PUT"
        case .post: return "POST"
        }
    }
}

struct Request<Response> {
    let url: URL
    let method: HttpMethod
    var headers: [String: String] = [:]
}

extension Request {
    static func postUserLogin(email: String, password:String) -> Self {
        var params = [String: Any]()
        params["username"] = email
        params["password"] = password
        let data = params.jsonData

        return Request(
            url: URL(string: NetworkingConstants.USER_LOGIN)!,
            method: .post(
                try? JSONEncoder().encode(data) //应该是其他什么???
            )
        )
    }
}

extension Dictionary {
    var jsonData: Data? {
        return try? JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted])
    }
}

extension Request {
    var urlRequest: URLRequest {
        var request = URLRequest(url: url)
        
        switch method {
        case .post(let data), .put(let data):
            request.httpBody = data
            
        case let .get(queryItems):
            //为简洁起见已删除
        default:
            break
        }
        
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.allHTTPHeaderFields = headers
        request.httpMethod = method.name
        return request
    }
}

extension URLSession {
    func decode<Value: Decodable>(
        _ request: Request<Value>,
        using decoder: JSONDecoder = .init()
    ) async throws -> Value {
        let decoded = Task.detached(priority: .userInitiated) {
            let (data, _) = try await self.data(for: request.urlRequest)
            try Task.checkCancellation()
            return try decoder.decode(Value.self, from: data)
        }
        return try await decoded.value
    }
}

问题是,请求的Body发送到服务器时看起来像这样:

{
    "username": "me@you.com",
    "password": "abcdef"
}

但实际上它看起来像这样:

"eyJwYXNzd29yZCI6ImFhYWEiLCJ1cZSI6InRhQHQuY29tIn0="

我做错了什么?这是我如何进行调用的:

let request: Request<UserLoginResponse> = .postUserLogin(email: "me@you.com", password: "abcdef")
let response = try? await URLSession.shared.decode(request)

request.httpBody看起来像这样:

Optional<Data>
    some : 74 bytes
        - count : 74
        pointer : 0x00007fcc74011200
          pointerValue : 140516096283136

如果你需要进一步的帮助,请提出具体的问题。

英文:

I'm trying to set up an API service. I have a simple login call that looks like this:

enum HttpMethod: Equatable {
case get([URLQueryItem])
case put(Data?)
case post(Data?)
var name: String {
switch self {
case .get: return &quot;GET&quot;
case .put: return &quot;PUT&quot;
case .post: return &quot;POST&quot;
}
}
}
struct Request&lt;Response&gt; {
let url: URL
let method: HttpMethod
var headers: [String: String] = [:]
}
extension Request {
static func postUserLogin(email: String, password:String) -&gt; Self {
var params = [String: Any]()
params[&quot;username&quot;] = email
params[&quot;password&quot;] = password
let data = params.jsonData
return Request(
url: URL(string: NetworkingConstants.USER_LOGIN)!,
method: .post(
try? JSONEncoder().encode(data) //should this be something else???
)
)
}
}
extension Dictionary {
var jsonData: Data? {
return try? JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted])
}
}
extension Request {
var urlRequest: URLRequest {
var request = URLRequest(url: url)
switch method {
case .post(let data), .put(let data):
request.httpBody = data
case let .get(queryItems):
//removed for brevity
default:
break
}
request.setValue(&quot;application/json&quot;, forHTTPHeaderField: &quot;Content-Type&quot;)
request.allHTTPHeaderFields = headers
request.httpMethod = method.name
return request
}
}
extension URLSession {
func decode&lt;Value: Decodable&gt;(
_ request: Request&lt;Value&gt;,
using decoder: JSONDecoder = .init()
) async throws -&gt; Value {
let decoded = Task.detached(priority: .userInitiated) {
let (data, _) = try await self.data(for: request.urlRequest)
try Task.checkCancellation()
return try decoder.decode(Value.self, from: data)
}
return try await decoded.value
}
}

The problem is, instead of the Body of the request being sent to the server looking like this:

{
&quot;username&quot;: &quot;me@you.com&quot;,
&quot;password&quot;: &quot;abcdef&quot;
}

It looks like this:

&quot;eyJwYXNzd29yZCI6ImFhYWEiLCJ1cZSI6InRhQHQuY29tIn0=&quot;

What am I doing wrong? Here's how I make the call:

let request: Request&lt;UserLoginResponse&gt; = .postUserLogin(email: &quot;me@you.com&quot;, password: &quot;abcdef&quot;)
let response = try? await URLSession.shared.decode(request)

request.httpBody looks like this:

▿ Optional&lt;Data&gt;
▿ some : 74 bytes
- count : 74
▿ pointer : 0x00007fcc74011200
- pointerValue : 140516096283136

答案1

得分: 3

Scott Thompson的答案在一般情况下是正确的,并且值得学习。在这种特定情况下,你可以简化一下,因为你恰好有一个[String: String]字典,它已经是可编码的。所以你不需要额外的编码类型:

static func postUserLogin(email: String, password: String) -> Self {
    var params = [String: String]()
    params["username"] = email
    params["password"] = password

    return Request(
        url: URL(string: NetworkingConstants.USER_LOGIN)!,
        method: .post(
            try? JSONEncoder().encode(params)
        )
    )
}

你的问题是你首先将params JSONSerialization编码为包含JSON字符的Data。然后你对该Data进行JSONEncoder编码,它默认使用Base64编码。这就是你得到的"ey..."输出。不需要两个步骤。你可以完全去掉jsonData,直接使用JSONEncoder。

英文:

Scott Thompson's answer is correct in the general case, and worth learning. In this specific case you can make it a bit simpler because you happen to have a [String: String] dictionary, which is already Encodable. So you don't need an extra encoding type:

static func postUserLogin(email: String, password:String) -&gt; Self {
var params = [String: String]()
params[&quot;username&quot;] = email
params[&quot;password&quot;] = password
return Request(
url: URL(string: NetworkingConstants.USER_LOGIN)!,
method: .post(
try? JSONEncoder().encode(params)
)
)
}

Your problem is that you're first JSONSerialization-encoding params into a Data that contains the JSON characters. You then JSONEncoder-encoding that Data, which by default Base64-encodes it. That's the "ey..." output you're getting. There's no need for two steps. You can get rid of jsonData entirely, and just use JSONEncoder directly.

答案2

得分: 2

JSONEncoderJSONDecoder 被设计成直接与 Swift 类型一起使用,允许您跳过构建通用字典或数组的需要,就像您在以下代码中所做的那样:

// 你应该使用一个 Swift 类型代替这个
var params = [String: Any]()
params["username"] = email
params["password"] = password
let data = params.jsonData

在你的情况下,你应该声明一个 struct 并将其标记为 Codable,然后对其进行编码(来自一个 playground):

struct UserAuthentication: Codable {
    let username: String
    let password: String
}

let email = "fred"
let password = "foobar"

let userAuthentication = UserAuthentication(username: email, password: password)
let encoder = JSONEncoder()

do {
    let encoded = try encoder.encode(userAuthentication)
    print(String(data: encoded, encoding: .utf8)!)
} catch (let error) {
    print("Json Encoding Error \(error)")
}

Swift 将使用属性的名称作为 JSON 键和它们的值作为 JSON 值来表示您的结构体(在本例中是 UserAuthentication 结构体)。

如果您想处理更复杂的情况,请参考:

编码和解码自定义类型

英文:

JSONEncoder and JSONDecoder are designed to work directly with Swift types allowing you to skip the need to build generic dictionaries or arrays as you are doing where you have this code:

// You should use a Swift type instead of this
var params = [String: Any]()
params[&quot;username&quot;] = email
params[&quot;password&quot;] = password
let data = params.jsonData

In your case you should declare a struct and mark it as Codable then encode that (from a playground):

struct UserAuthentication: Codable {
let username: String
let password: String
}
let email = &quot;fred&quot;
let password = &quot;foobar&quot;
let userAuthentication = UserAuthentication(username: email, password: password)
let encoder = JSONEncoder()
do {
let encoded = try encoder.encode(userAuthentication)
print(String(data: encoded, encoding: .utf8)!)
} catch (let error) {
print(&quot;Json Encoding Error \(error)&quot;)
}

Swift will represent your struct (in this case the UserAuthentication struct) as JSON using the names of the properties as the JSON keys and their values as the JSON values.

If you want to handle more complex cases, see:

Encoding and Decoding Custom Types

huangapple
  • 本文由 发表于 2023年7月11日 05:00:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76657315.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定