【Swift】JSONの1や0をBool(真偽値)に変換する方法!decodeIfPresent

【Swift】JSONの1や0をBool(真偽値)に変換する方法!decodeIfPresent

この記事からわかること

  • JSONDecoder使い方
  • JSONの値が10の時にBool(真偽値)へ変換する方法
  • APIで真偽値を扱う
  • decodeIfPresentメソッドの使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

JSONデータの1や0をBool(真偽値)に変換する方法

iOSアプリからAPIと連携しJSONデータを取得しSwiftで扱えるオブジェクトに変換する際に、JSONの値が以下のように10の場合がありました。ただInt型として扱うなら良いですがBool型を意味する値だったので10を渡された場合にBool型に変換する方法をまとめていきます。

JSONは以下のように値部分に数値の10が渡されます。

対象のJSON

{
    "key1": 1,
    "key2": 0,
}

マッピング用のクラスにBool型への変換のロジックを含ませます。変換したいキーにあったプロパティを準備しCodingKeyも定義します。CodingKeyがないと後続の処理でエラーが発生します。

JSONからオブジェクトへの変換作業は自動で行われますが、専用のイニシャライザを用意することで変換時の値を操作することが可能になります。中ではdecodeIfPresent(_:forKey:)メソッドを使用してキー1つ1つをデコードしていきます。

マッピング用クラス

struct MyObject: Decodable {
    let key1: Bool
    let key2: Bool

    enum CodingKeys: String, CodingKey {
        case key1, key2
    }

    // Bool型へ変換用のイニシャライザ
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let intValue = try container.decodeIfPresent(Int.self, forKey: .key1) {
            key1 = (intValue == 1) // 1ならtrue
        } else {
            key1 = false
        }
        if let intValue = try container.decodeIfPresent(Int.self, forKey: .key2) {
            key2 = (intValue == 1) // 1ならtrue
        } else {
            key2 = false
        }
    }
}

公式リファレンス:decodeIfPresent(_:forKey:)

今回は10の際にBool型へ変換していますが、値によって任意の値に変換させることも可能になります。

使用例

// JSONデータ
let jsonData = """
{
    "key1": 1,
    "key2": 0,
}
""".data(using: .utf8)!

// JSONデコード
do {
    let decoder = JSONDecoder()
    let instance = try decoder.decode(MyObject.self, from: jsonData)
    
    print(instance.key1) // true
    print(instance.key2) // false
    
} catch {
    print("JSON decoding error: \(error)")
}

decodeIfPresentのtry

func decodeIfPresent(_ type: Bool.Type, forKey key: Self.Key) throws -> Bool?

decodeIfPresentメソッドは例外を投げる可能性があるメソッドです。そのためJSONの値と正しいデータ型を指定していれば成功しますが、異なるデータ型を指定すると失敗してしまいます。tryだと失敗した際にデコード自体が失敗になってしまうのでtry?を使うことで失敗してもnilが格納されてdecodeメソッドも継続させることが可能です。

let intValue = try container.decodeIfPresent(Int.self, forKey: .key1) // 成功
let stringValue = try container.decodeIfPresent(String.self, forKey: .key1) // 失敗
// ここで例外が発生しdecodeメソッドが終了してしまう。
let intValue = try container.decodeIfPresent(Int.self, forKey: .key1) // 成功
let stringValue = try? container.decodeIfPresent(String.self, forKey: .key1) // 失敗
// try?を使うことで失敗してもnilが格納されてdecodeメソッドも継続する

JSONに複数の値がある場合

取得できるJSONがBool型への変換だけでなく以下のようにString型やInt型に変換したい場合もあると思います。

{
    "key1": 1,
    "key2": 0,
    "key3": "test",
    "key4": 20,
}

この場合も実装方法はイニシャライザの中にキー1つ1つデコードする処理を記述する必要があります。構造体のプロパティに初期値があればデコードしなくてもその初期値が使用されてしまいますがデコードが不要にもできます。

struct MyObject: Decodable {
    let key1: Bool
    let key2: Bool
    let key3: String
    let key4: Int

    enum CodingKeys: String, CodingKey {
        case key1, key2, key3, key4
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // Boolへ任意のデコード
        if let intValue = try container.decodeIfPresent(Int.self, forKey: .key1) {
            key1 = (intValue == 1)
        } else {
            key1 = false
        }
        if let intValue = try container.decodeIfPresent(Int.self, forKey: .key2) {
            key2 = (intValue == 1)
        } else {
            key2 = false
        }
        
        // 通常のデコード
        key3 = try container.decodeIfPresent(String.self, forKey: .key3) ?? ""
        key4 = try container.decodeIfPresent(Int.self, forKey: .key4) ?? 0
    }
}

エンコードする際にBool型をInt型に変換する方法

反対にSwiftのオブジェクトのプロパティがBool型の際にJSONで10などのように任意の形に変換することも可能です。まずはオブジェクトにencode(to:)メソッドを追加します。

struct MyObject: Decodable, Encodable {
    let key1: Bool
    let key2: Bool

    enum CodingKeys: String, CodingKey {
        case key1, key2
    }

    // Bool型へ変換用のイニシャライザ
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let intValue = try container.decodeIfPresent(Int.self, forKey: .key1) {
            key1 = (intValue == 1) // 1ならtrue
        } else {
            key1 = false
        }
        if let intValue = try container.decodeIfPresent(Int.self, forKey: .key2) {
            key2 = (intValue == 1) // 1ならtrue
        } else {
            key2 = false
        }
    }
    
    // エンコード
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        // Bool型から0/1へ変換してエンコード
        let intValue = key1 ? 1 : 0
        try container.encode(intValue, forKey: .key1)
        // Convert Bool to Int for encoding
        let intValue2 = key2 ? 1 : 0
        try container.encode(intValue2, forKey: .key2)
        
        // カスタムで変換しない場合のエンコード
        // try container.encode(key3, forKey: .key3)
    }
}

ここでencode(_:forKey:)メソッドを使用してキーに応じた変換値を指定します。今回はBoolから1/0なので上記のような実装になっています。

公式リファレンス:encode(_:forKey:)

func encode(
    _ object: Any?,
    forKey key: String
)

あとは普通にエンコード処理を実装するだけで変換することが可能です。

// JSONデコード
do {
    let decoder = JSONDecoder()
    let instance = try decoder.decode(MyObject.self, from: jsonData)
    
    let encoder = JSONEncoder()
    // フォーマットを指定
    encoder.outputFormatting = .prettyPrinted
    // エンコード
    let jsonData = try encoder.encode(instance)
    print(String(data: jsonData , encoding: .utf8)!)
    
} catch {
    print("JSON decoding error: \(error)")
}

変換されたJSON

{
  "key1" : 1,
  "key2" : 0
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index