Skip to content

sweepty

Decodable - 여러 타입을 가지는 키 만들기

1 min read

medium에 [iOS] Codable을 이용하여 JSON decoding하기 라는 포스팅을 작성한 적이 있었기 때문에 자신만만했지만 난관이 꽤 많았고 삽질이란 삽질은 다 했기 때문에 다시 정리하려고 한다.

위에서 언급한 포스팅에 작성한 것처럼 convertFromSnakeCase 를 사용했다.

1decoder.keyDecodingStrategy = .convertFromSnakeCase

API 문서에 써 있는 JSON 구조는 다음과 같았다.

1// Response - success
2{
3 "jsonrpc": "2.0",
4 "id": 1234,
5 "result": {
6 "version": "0.1a",
7 "prev_block_hash": "48757af881f76c858890fb41934bee228ad50a71707154a482826c39b8560d4b",
8 "merkle_tree_root_hash": "fabc1884932cf52f657475b6d62adcbce5661754ff1a9d50f13f0c49c7d48c0c",
9 "time_stamp": 1516498781094429,
10 "confirmed_transaction_list": [
11 {
12 "version": "0x3",
13 "from": "hxbe258ceb872e08851f1f59694dac2558708ece11",
14 "to": "cxb0776ee37f5b45bfaea8cff1d8232fbb6122ec32",
15 "value": "0xde0b6b3a7640000",
16 "stepLimit": "0x12345",
17 "timestamp": "0x563a6cf330136",
18 "nid": "0x3",
19 "nonce": "0x1",
20 "signature": "VAia7YZ2Ji6igKWzjR2YsGa2m53nKPrfK7uXYW78QLE+ATehAVZPC40szvAiA6NEU5gCYB4c4qaQzqDh2ugcHgA=",
21 "txHash": "0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238",
22 "dataType": "call",
23 "data": {
24 "method": "transfer",
25 "params": {
26 "to": "hxab2d8215eab14bc6bdd8bfb2c8151257032ecd8b",
27 "value": "0x1"
28 }
29 }
30 }
31 ],
32 "block_hash": "1fcf7c34dc875681761bdaa5d75d770e78e8166b5c4f06c226c53300cbe85f57",
33 "height": 3,
34 "peer_id": "hx86aba2210918a9b116973f3c4b27c41a54d5dafe",
35 "signature": "MEQCICT8mTIL6pRwMWsJjSBHcl4QYiSgG8+0H3U32+05mO9HAiBOhIfBdHNm71WpAZYwJWwQbPVVXFJ8clXGKT3ScDWcvw=="
36 }
37}

그래서 이 문서에 맞춰서 아래와 같이 Decodable 모델을 구성했다.

1open class ConfirmedTransactionList: Decodable {
2 public var from: String
3 public var to: String
4 public var timestamp: String
5 public var signature: String
6 public var txHash: String
7
8 public var version: String?
9 public var nid: String?
10 public var stepLimit: String?
11 public var value: String?
12
13 public var nonce: String?
14 public var dataType: String?
15 public var data: DataInfo?
16
17 public var fee: String?
18 public var method: String?
19
20 open class DataInfo: Decodable {
21 public var method: String?
22 public var params: [String: String]?
23 }
24}

잘 되는 줄 알고 이제 예제를 만들어보려고 하는데 Parsing error가 떴다. :tired_face:

대체 왜…. 어제까지만 해도 잘 되던게… 안될까ㅠㅠㅠㅠ하고 그 문제가 일어나는 부분의 JSON을 보았더니 이런 식이었다.

1{
2 "jsonrpc": "2.0",
3 "result": {
4 "version": "0.1a",
5 "prev_block_hash": "3141e30db0b7036cfd750d9105d90ef95289c3a6f7e896999a4997c68c002fb8",
6 "merkle_tree_root_hash": "d5a7106af08f96871c7c2bd6d5e16d5159486aff73f8d4ef84ff35e32d8d6b39",
7 "time_stamp": 1550764885497421,
8 "confirmed_transaction_list": [
9 {
10 "from": "hx04bebefa2c8c8378c20393d8259afe38a414e160",
11 "to": "hx605b5ddd50a4b0314b93bcd80561aa5d37d4b46f",
12 "value": "0x3635c9adc5dea00000",
13 "version": "0x3",
14 "nid": "0x1",
15 "stepLimit": "0x193e8",
16 "timestamp": "0x5826996223350",
17 "data": "0x49434f4e65782074696c204c6564676572",
18 "dataType": "message",
19 "signature": "jxD3/Pe1OfIrgfsPgxReWxMbhSdTc8inp7mhoOps6ItS71vracx8UZdOrwgkoS7sFV1F8+ldN3LNQd2BZ9CykwE=",
20 "txHash": "0xd5a7106af08f96871c7c2bd6d5e16d5159486aff73f8d4ef84ff35e32d8d6b39"
21 }
22 ],
23 "block_hash": "bfbb0ae7770247bb2c7ca6d3b3fbcff398f4f3bc8bd68b7d511bc5e1d8499297",
24 "height": 202131,
25 "peer_id": "hx7563e2514a0865630216903c5fd166ec0fdb217a",
26 "signature": "eSem6PHyHWxWyOretx0PAX+xEYyC+G0iuoS0ooIziy5iEzW4XrliQrHY59igC4d8krszw9TJoAYUAGsViZsLOwA="
27 },
28 "id": 1234
29}

data 라는 값이 두가지의 타입을 가져야하는 상황이었다.

그래서 갓구글에 검색해서 다음과 같은 답변을 찾아냈다.

https://stackoverflow.com/a/47319012

https://stackoverflow.com/a/50067514

enum을 이용하여 해결했다.

1open class ConfirmedTransactionList: Decodable {
2 public var from: String
3 public var to: String
4 public var timestamp: String
5 public var signature: String
6 public var txHash: String
7
8 public var version: String?
9 public var nid: String?
10 public var stepLimit: String?
11 public var value: String?
12
13 public var nonce: String?
14 public var dataType: String?
15
16 public var data: DataValue?
17
18 public var fee: String?
19 public var method: String?
20
21 public enum DataValue: Decodable {
22 case string(String)
23 case dataInfo(DataInfo)
24
25 public init(from decoder: Decoder) throws {
26 if let string = try? decoder.singleValueContainer().decode(String.self) {
27 self = .string(string)
28 return
29 }
30
31 if let dataInfo = try? decoder.singleValueContainer().decode(DataInfo.self) {
32 self = .dataInfo(dataInfo)
33 return
34 }
35 throw DataValueError.missingValue
36 }
37 public enum DataValueError: Error {
38 case missingValue
39 }
40 }
41
42 open class DataInfo: Decodable {
43 public var method: String?
44 public var params: [String: String]?
45 }
46}

data에 있는 값 찾아오는 방법은 다음과 같다.

1let val = value.result.confirmedTransactionList.first?.data
2
3switch val {
4 case .string(let data)?:
5 print("String: \(data)")
6 case .dataInfo(let data)?:
7 print("DataInfo: \(data.method) \(data.params)")
8 case .none:
9 print("None")
10}