post Image
Codableで色々なJSONに対応する

元のJSONの構造のまま利用できればいいけど、構造を変えようと思うと結構コード量が増えてくる。
WebAPIのレスポンスを利用するだけならDecodableに準拠するだけで十分だと思いました。

サンプルコードはすべてPlaygroundで実行できます。

基本

Codableに準拠していて、プロパティに使える型

Bool, Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Int, Double, String

あとは上記の型を要素に持つOptional, Array, Dictionary

※他にもあったら随時追記します

let data = """
{
    "model": "iPhone X",
    "displaySize": 5.8,
    "capacities": [64, 256],
    "biometricsAuth": "Face ID"
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: String
    var displaySize: Float
    var capacities: [Int]
    var biometricsAuth: String? // nullの場合がある or キーがない場合がある
}


let device = try? JSONDecoder().decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // リーダブルな出力
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)
ルートがArrayのJSON.swift
let list = """
[
    {
        "model": "iPhone 3G",
        "displaySize": 3.5,
        "capacities": [8, 16],
        "biometricsAuth": null
    },
    {
        "model": "iPhone 4",
        "displaySize": 3.5,
        "capacities": [8, 16, 32]
    }
]
""".data(using: .utf8)!


let devices = try? JSONDecoder().decode([Device].self, from: list)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try! encoder.encode(devices)
print(String(data: encoded, encoding: .utf8)!)

値にenumを使ったり、structをネストさせる

enumを利用する場合、RawRepresentableに準拠していてRawValue

Bool, Int, Int8, Int16, Int32, Int64, UInt, UInt8, UInt16, UInt32, UInt64, Int, Double, String

であれば、init(from:), encode(to:) のデフォルト実装が用意されてるので簡単。

let data = """
{
    "model": "iPhone X",
    "capacities": [64, 256],
    "size": {
        "height": 143,
        "width": 70,
        "depth": 7
    }
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: Model
    var capacities: [Capacity]
    var size: Size

    enum Model: String, Codable {
        case iPhoneX = "iPhone X"
        case iPhone8 = "iPhone 8"
        case iPhone8Plus = "iPhone 8 Plus"
    }

    enum Capacity: Int, Codable {
        case _64 = 64
        case _256 = 256
    }

    struct Size: Codable {
        var height: Int
        var width: Int
        var depth: Int
    }
}


let device = try? JSONDecoder().decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)

enumのassociated valueを利用する

いずれかのキーでレスポンスが返ってくるみたいな場合に

// data1 か data2 どちらかの形式でレスポンスが返る想定
let data1 = """
{"str": "文字列"}
""".data(using: .utf8)!

let data2 = """
{"num": 777}
""".data(using: .utf8)!


enum Response: Codable {
    case str(String)
    case num(Int)

    private enum CodingKeys: String, CodingKey {
        case str
        case num
    }

    enum CodingError: Error {
        case decoding(String)
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        if let value = try? values.decode(String.self, forKey: .str) {
            self = .str(value)
            return
        }

        if let value = try? values.decode(Int.self, forKey: .num) {
            self = .num(value)
            return
        }

        throw CodingError.decoding("\(dump(values))")
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        switch self {
        case .str(let value):
            try container.encode(value, forKey: .str)
        case .num(let value):
            try container.encode(value, forKey: .num)
        }
    }
}


let str = try? JSONDecoder().decode(Response.self, from: data1)
let num = try? JSONDecoder().decode(Response.self, from: data2)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encodedStr = try! encoder.encode(str)
print(String(data: encodedStr, encoding: .utf8)!)

let encodedNum = try! encoder.encode(num)
print(String(data: encodedNum, encoding: .utf8)!)

Date型のフォーマット

JSONDecoderdateDecodingStrategyプロパティ, JSONEncoderdateEncodingStrategyプロパティでDate型のパース方法を指定できる。

let data = """
{
    "model": "iPhone X",
    "releaseDate": "2017-10-19T11:53:36Z"
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: String
    var releaseDate: Date
}


let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let device = try? decoder.decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .iso8601
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)

型変換

JSONで数値の文字列"123"が返ってくるが、SwiftではIntで扱いたい場合など

let data = """
{
    "model": "iPhone X",
    "capacity": "64"
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: String
    var capacity: Int

    private enum CodingKeys: String, CodingKey {
        case model
        case capacity
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        model = try values.decode(String.self, forKey: .model)
        capacity = Int(try values.decode(String.self, forKey: .capacity)) ?? 0
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(model, forKey: .model)
        try container.encode(capacity.description, forKey: .capacity)
    }
}


let device = try? JSONDecoder().decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)

JSONのkeyと、structのpropertyのマッピング

let data = """
{
    "model_name": "iPhone X"
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: String

    private enum CodingKeys: String, CodingKey {
        case model = "model_name"
    }
}


let device = try? JSONDecoder().decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)

ネストしたJSON <=> Flatな構造体

let data = """
{
    "model": "iPhone X",
    "specs": {
        "color": "Space Gray",
        "capacity": 64
    }
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: String
    var color: Color
    var capacity: Int

    enum Color: String, Codable {
        case spaceGray = "Space Gray"
        case silver = "Silver"
    }

    private enum CodingKeys: String, CodingKey {
        case model
        case specs
    }

    private enum SpecsKeys: String, CodingKey {
        case color
        case capacity
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        model = try values.decode(String.self, forKey: .model)

        let specs = try values.nestedContainer(keyedBy: SpecsKeys.self, forKey: .specs)
        color    = try specs.decode(Color.self, forKey: .color)
        capacity = try specs.decode(Int.self, forKey: .capacity)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(model, forKey: .model)

        var specs = container.nestedContainer(keyedBy: SpecsKeys.self, forKey: .specs)
        try specs.encode(color, forKey: .color)
        try specs.encode(capacity, forKey: .capacity)
    }
}


let device = try? JSONDecoder().decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)

FlatなJSON <=> ネストした構造体

let data = """
{
    "model": "iPhone X",
    "height": 143,
    "width": 70,
    "depth": 7
}
""".data(using: .utf8)!


struct Device: Codable {
    var model: String
    var size: Size

    struct Size: Codable {
        var height: Int
        var width: Int
        var depth: Int
    }

    private enum CodingKeys: String, CodingKey {
        case model
        case height
        case width
        case depth
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        model = try values.decode(String.self, forKey: .model)
        size = try Size(from: decoder)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(model, forKey: .model)
        try container.encode(size.height, forKey: .height)
        try container.encode(size.width, forKey: .width)
        try container.encode(size.depth, forKey: .depth)
    }
}


let device = try? JSONDecoder().decode(Device.self, from: data)

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encoded = try! encoder.encode(device)
print(String(data: encoded, encoding: .utf8)!)

参考

Codable – Swift Standard Library | Apple Developer Documentation
Swift4のJSONDecorderは、Date等のパース方法をカスタマイズできるみたい – Qiita
Codable in Swift 4.0 – Sarun W. – Medium


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。