簡體   English   中英

如何使用Swift Codable處理部分動態JSON?

[英]How to handle partially dynamic JSON with Swift Codable?

我有一些通過websocket連接進入的JSON消息。

// sample message
{
  type: "person",
  data: {
    name: "john"
  }
}

// some other message
{
  type: "location",
  data: {
    x: 101,
    y: 56
  }
}

如何使用Swift 4和Codable協議將這些消息轉換為正確的結構?

在Go中我可以做類似的事情:“嘿,此刻我只關心type字段,而我對其余部分( data部分)不感興趣。” 它看起來像這樣

type Message struct {
  Type string `json:"type"`
  Data json.RawMessage `json:"data"`
}

如您所見, Data的類型為json.RawMessage ,稍后可以對其進行解析。 這是一個完整的示例https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal

我可以在Swift中做類似的事情嗎? 喜歡(尚未嘗試過)

struct Message: Codable {
  var type: String
  var data: [String: Any]
}

然后switch type以將字典轉換為適當的結構。 那會有用嗎?

我不會依賴Dictionary 我會使用自定義類型。

例如,讓我們假設:

  • 你知道你要回來的對象(因為請求的性質);

  • 除了data內容之外,這兩種類型的響應確實返回相同的結構。

在這種情況下,您可以使用一個非常簡單的通用模式:

struct Person: Decodable {
    let name: String
}

struct Location: Decodable {
    let x: Int
    let y: Int
}

struct ServerResponse<T: Decodable>: Decodable {
    let type: String
    let data: T
}

然后,當您想要解析一個Person的響應時,它將是:

let data = json.data(using: .utf8)!
do {
    let responseObject = try JSONDecoder().decode(ServerResponse<Person>.self, from: data)

    let person = responseObject.data
    print(person)
} catch let parseError {
    print(parseError)
}

或者解析一個Location

do {
    let responseObject = try JSONDecoder().decode(ServerResponse<Location>.self, from: data)

    let location = responseObject.data
    print(location)
} catch let parseError {
    print(parseError)
}

可以接受更復雜的模式(例如,基於它遇到的type值動態解析data類型),但除非必要,否則我不傾向於追求這樣的模式。 這是一種很好的,簡單的方法,它可以實現典型模式,您可以在其中了解特定請求的相關響應類型。


如果您希望可以使用從data值解析的內容來驗證type值。 考慮:

enum PayloadType: String, Decodable {
    case person = "person"
    case location = "location"
}

protocol Payload: Decodable {
    static var payloadType: PayloadType { get }
}

struct Person: Payload {
    let name: String
    static let payloadType = PayloadType.person
}

struct Location: Payload {
    let x: Int
    let y: Int
    static let payloadType = PayloadType.location
}

struct ServerResponse<T: Payload>: Decodable {
    let type: PayloadType
    let data: T
}

然后,您的parse函數不僅可以解析正確的data結構,還可以確認type值,例如:

enum ParseError: Error {
    case wrongPayloadType
}

func parse<T: Payload>(_ data: Data) throws -> T {
    let responseObject = try JSONDecoder().decode(ServerResponse<T>.self, from: data)

    guard responseObject.type == T.payloadType else {
        throw ParseError.wrongPayloadType
    }

    return responseObject.data
}

然后你可以像這樣稱呼它:

do {
    let location: Location = try parse(data)
    print(location)
} catch let parseError {
    print(parseError)
}

這不僅返回Location對象,還驗證服務器響應中type的值。 我不確定這是值得的,但如果你想這樣做,那就是一種方法。


如果在處理JSON時你真的不知道類型,那么你只需要編寫一個首先解析該typeinit(coder:) ,然后根據包含的type的值解析data

enum PayloadType: String, Decodable {
    case person = "person"
    case location = "location"
}

protocol Payload: Decodable {
    static var payloadType: PayloadType { get }
}

struct Person: Payload {
    let name: String
    static let payloadType = PayloadType.person
}

struct Location: Payload {
    let x: Int
    let y: Int
    static let payloadType = PayloadType.location
}

struct ServerResponse: Decodable {
    let type: PayloadType
    let data: Payload

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        type = try values.decode(PayloadType.self, forKey: .type)
        switch type {
        case .person:
            data = try values.decode(Person.self, forKey: .data)
        case .location:
            data = try values.decode(Location.self, forKey: .data)
        }
    }

    enum CodingKeys: String, CodingKey {
        case type, data
    }

}

然后你可以做以下事情:

do {
    let responseObject = try JSONDecoder().decode(ServerResponse.self, from: data)
    let payload = responseObject.data
    if payload is Location {
        print("location:", payload)
    } else if payload is Person {
        print("person:", payload)
    }
} catch let parseError {
    print(parseError)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM