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
に依存しません。カスタムタイプを使用します。
たとえば、次のように仮定します。
どのオブジェクトを取得するかがわかっています(リクエストの性質上)。そして
2つのタイプの応答は、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)
}
_
楽しませることができるより複雑なパターンがあります(たとえば、遭遇したdata
値に基づいたtype
型の動的解析)が、必要でない限り、そのようなパターンを追求する傾向はありません。これは、特定のリクエストに関連付けられた応答タイプがわかっている典型的なパターンを実現する、優れたシンプルなアプローチです。
必要に応じて、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<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を処理するときにタイプが本当にわからない場合は、最初にtype
を解析し、次にdata
を解析するinit(coder:)
を記述する必要があります。 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: 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)
}
_