本文介绍了如何使用Swift Codable处理部分动态JSON?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我通过Websocket连接收到了一些JSON消息。

I've got some JSON messages coming in over a websocket connection.

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

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

如何使用Swift 4和Codable协议将这些消息转换为适当的结构?

How can I convert those messages into proper structs using Swift 4 and the Codable protocol?

在Go中,我可以执行类似的操作:嘿,此刻我只关心 type 字段,而我对其余的内容不感兴趣( code> data 部分)。看起来像这样

In Go I can do something like: "Hey at the moment I only care about the type field and I'm not interested in the rest (the data part)." It would look like this

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

如您所见数据的类型为 json.RawMessage ,以后可以对其进行解析。这是完整的示例。

As you can see Data is of type json.RawMessage which can be parsed later on. Here is a full example https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal.

我可以在Swift中做类似的事情吗?像(尚未尝试过)

Can I do something similar in Swift? Like (haven't tried it yet)

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

然后切换类型的 可以将字典转换为适当的结构。会行得通吗?

Then switch on the type to convert the dictionary into proper structs. Would that work?

推荐答案

我不会依赖 Dictionary 。我会使用自定义类型。

I wouldn't rely upon a Dictionary. I'd use custom types.

例如,让我们假设:


  • 您知道要取回哪个对象(由于请求的性质);和

  • you know which object you're going to get back (because of the nature of the request); and

这两种类型的响应实际上返回相同的结构,除了 data 的内容之外。

the two types of response truly return identical structures except the contents of the data.

在这种情况下,您可以使用非常简单的通用模式:

In that case, you might use a very simple generic pattern:

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
}

然后,当您想用解析响应时人,应该是:

And then, when you want to parse a response with a Person, it would be:

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)
}

或解析位置

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

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

还有更多可以让人接受的复杂模式(例如,根据遇到的 type 值动态解析 data 类型),但是我除非必要,否则不会倾向于采用这种模式。这是一种很好的简单方法,可以完成典型的模式,在这种模式下您可以知道特定请求的关联响应类型。

There are more complicated patterns one could entertain (e.g. dynamic parsing of the data type based upon the type value it encountered), but I wouldn't be inclined to pursue such patterns unless necessary. This is a nice, simple approach that accomplishes typical pattern where you know the associated response type for a particular request.

如果您希望可以使用从数据值解析的内容来验证 type 值。考虑:

If you wanted you could validate the type value with what was parsed from the data value. Consider:

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
}

然后,解析您的 函数不仅可以解析正确的 data 结构,还可以确认 type 值,例如:

Then, your parse function could not only parse the right data structure, but confirm the type value, e.g.:

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
}

然后您可以这样称呼它:

And then you could call it like so:

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

这不仅会返回 Location 对象,还会验证 type 在服务器响应中。我不确定是否值得付出努力,但是如果您愿意这样做,那就是一种方法。

That not only returns the Location object, but also validates the value for type in the server response. I'm not sure it's worth the effort, but in case you wanted to do so, that's an approach.

如果您在处理JSON时确实不知道类型,那么您只需要编写 init(coder:)首先解析 type ,然后根据 type 包含的值来解析数据

If you really don't know the type when processing the JSON, then you just need to write an init(coder:) that first parses the type, and then parses the data depending upon the value that type contained:

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
    }

}

然后您可以执行以下操作:

And then you can do things like:

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)
}

这篇关于如何使用Swift Codable处理部分动态JSON?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 07:27