web-dev-qa-db-ja.com

JSON解析にDecodableを使用する場合のオプションとdecodeIfPresentの違いは何ですか?

Swift 4から初めてCodableプロトコルを使用しています。decodeIfPresentからのDecodableの使用を理解できません。

/// Decodes a value of the given type for the given key, if present.
///
/// This method returns `nil` if the container does not have a value associated with `key`, or if the value is null. The difference between these states can be distinguished with a `contains(_:)` call.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A decoded value of the requested type, or `nil` if the `Decoder` does not have an entry associated with the given key, or if the value is a null value.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value is not convertible to the requested type.
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String?

ここでは、値が関連付けられたキーに存在しない場合、nilを返すことを提案しています。これが唯一の理由である場合、値が応答に存在しない場合はオプション変数がnilに設定されるため、オプションプロパティとの違い.

21
technerd

これらの2行のコードには微妙ですが、重要な違いがあります。

// Exhibit 1
foo = try container.decode(Int?.self, forKey: .foo)
// Exhibit 2
foo = try container.decodeIfPresent(Int.self, forKey: .foo)

図1は解析します:

{
  "foo": null,
  "bar": "something"
}

しかしnot

{
  "bar": "something"
}

一方、展示2は両方を喜んで解析します。したがって、JSONパーサーの通常の使用例では、モデルのすべてのオプションに対してdecodeIfPresentが必要になります。

61
Gunter Hager

はい、@ Sweeperのコメントは理にかなっています。

私の理解に従って説明しようと思います。

public class User : Decodable{

    public var firstName:String
    public var lastName:String
    public var middleName:String?
    public var address:String
    public var contactNumber:String


    public enum UserResponseKeys: String, CodingKey{
        case firstName = "first_name"
        case lastName = "last_name"
        case middleName = "middle_name"
        case address = "address"
        case contactNumber = "contact_number"
    }

    public required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: UserResponseKeys.self)

        self.firstName = try container.decode(String.self, forKey: .firstName)
        self.lastName = try container.decode(String.self, forKey: .lastName)
        self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)
        self.address = try container.decode(String.self, forKey: .address)
        self.contactNumber = try container.decode(String.self, forKey: .contactNumber)
    }

}

上記は私のUserクラスです。このクラスでは、middleNameをオプションのパラメーターとしてマークしました。これは、JSON応答が応答にmiddleNameキーと値のペアを提供しない可能性があるため、 decodeIfPresentを使用できます。

self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)

一方、必須フィールドである他の変数については、そのためにオプションを使用する必要がないことが確実です。そのメソッドはオプションを返さないため、decodeのみを使用しました。

public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String

上記のdecode関数はStringを返し、decodeIfPresent関数はString?ので、オプションの変数を使用して保存できます。

つまり、最終的な結論は、サービスレスポンスコントラクトが不明な場合、またはJSONレスポンスとパラメーターが知らないうちに変更される可能性のあるサードパーティサービスを扱う場合、decodeIfPresentを使用して、応答で特定のパラメータの不在を処理し、値をnilとして設定します。

6
technerd